<!doctype html>
<html>
<head>
<meta charset='UTF-8'><meta name='viewport' content='width=device-width initial-scale=1'>
<title>README.md</title><link href='http://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,700,400&subset=latin,latin-ext' rel='stylesheet' type='text/css'><style type='text/css'>html, body {overflow-x: initial !important;}.CodeMirror { height: auto; }
.CodeMirror-scroll { overflow-y: hidden; overflow-x: auto; }
.CodeMirror-lines { padding: 4px 0px; }
.CodeMirror pre { }
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { background-color: white; }
.CodeMirror-gutters { border-right: 1px solid rgb(221, 221, 221); background-color: rgb(247, 247, 247); white-space: nowrap; }
.CodeMirror-linenumbers { }
.CodeMirror-linenumber { padding: 0px 3px 0px 5px; text-align: right; color: rgb(153, 153, 153); }
.CodeMirror div.CodeMirror-cursor { border-left: 1px solid black; z-index: 3; }
.CodeMirror div.CodeMirror-secondarycursor { border-left: 1px solid silver; }
.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { width: auto; border: 0px; background: rgb(119, 238, 119); z-index: 1; }
.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite { }
.cm-tab { display: inline-block; }
.cm-s-typora-default .cm-header, .cm-s-typora-default .cm-property { color: rgb(217, 79, 138); }
.cm-s-typora-default pre.cm-header1:not(.cm-atom) :not(.cm-overlay) { font-size: 2rem; line-height: 2rem; }
.cm-s-typora-default pre.cm-header2:not(.cm-atom) :not(.cm-overlay) { font-size: 1.4rem; line-height: 1.4rem; }
.cm-s-typora-default .cm-atom, .cm-s-typora-default .cm-number { color: rgb(149, 132, 134); }
.cm-s-typora-default .cm-table-row, .cm-s-typora-default .cm-block-start { font-family: monospace; }
.cm-s-typora-default .cm-comment, .cm-s-typora-default .cm-code { color: rgb(74, 90, 159); font-family: monospace; }
.cm-s-typora-default .cm-tag { color: rgb(169, 68, 66); }
.cm-s-typora-default .cm-string { color: rgb(126, 134, 169); }
.cm-s-typora-default .cm-link { color: rgb(196, 122, 15); text-decoration: underline; }
.cm-s-typora-default .cm-variable-2, .cm-s-typora-default .cm-variable-1 { color: inherit; }
.cm-s-typora-default .cm-overlay { font-size: 1rem; font-family: monospace; }
.CodeMirror.cm-s-typora-default div.CodeMirror-cursor { border-left: 3px solid rgb(228, 98, 154); }
.cm-s-typora-default .CodeMirror-activeline-background { left: -60px; right: -30px; background: rgba(204, 204, 204, 0.2); }
.cm-s-typora-default .CodeMirror-gutters { border-right: none; background-color: inherit; }
.cm-s-typora-default .cm-trailing-space-new-line::after, .cm-startspace::after, .cm-starttab .cm-tab::after { content: "•"; position: absolute; left: 0px; opacity: 0; font-family: LetterGothicStd, monospace; }
.os-windows .cm-startspace::after, .os-windows .cm-starttab .cm-tab::after { left: -0.1em; }
.cm-starttab .cm-tab::after { content: " "; }
.cm-startspace, .cm-tab, .cm-starttab, .cm-trailing-space-a, .cm-trailing-space-b, .cm-trailing-space-new-line { font-family: monospace; position: relative; }
.cm-s-typora-default .cm-trailing-space-new-line::after { content: "↓"; opacity: 0.3; }
.cm-s-inner .cm-keyword { color: rgb(119, 0, 136); }
.cm-s-inner .cm-atom, .cm-s-inner.cm-atom { color: rgb(34, 17, 153); }
.cm-s-inner .cm-number { color: rgb(17, 102, 68); }
.cm-s-inner .cm-def { color: rgb(0, 0, 255); }
.cm-s-inner .cm-variable { color: black; }
.cm-s-inner .cm-variable-2 { color: rgb(0, 85, 170); }
.cm-s-inner .cm-variable-3 { color: rgb(0, 136, 85); }
.cm-s-inner .cm-property { color: black; }
.cm-s-inner .cm-operator { color: rgb(152, 26, 26); }
.cm-s-inner .cm-comment, .cm-s-inner.cm-comment { color: rgb(170, 85, 0); }
.cm-s-inner .cm-string { color: rgb(170, 17, 17); }
.cm-s-inner .cm-string-2 { color: rgb(255, 85, 0); }
.cm-s-inner .cm-meta { color: rgb(85, 85, 85); }
.cm-s-inner .cm-qualifier { color: rgb(85, 85, 85); }
.cm-s-inner .cm-builtin { color: rgb(51, 0, 170); }
.cm-s-inner .cm-bracket { color: rgb(153, 153, 119); }
.cm-s-inner .cm-tag { color: rgb(17, 119, 0); }
.cm-s-inner .cm-attribute { color: rgb(0, 0, 204); }
.cm-s-inner .cm-header, .cm-s-inner.cm-header { color: blue; }
.cm-s-inner .cm-quote, .cm-s-inner.cm-quote { color: rgb(0, 153, 0); }
.cm-s-inner .cm-hr, .cm-s-inner.cm-hr { color: rgb(153, 153, 153); }
.cm-s-inner .cm-link, .cm-s-inner.cm-link { color: rgb(0, 0, 204); }
.cm-negative { color: rgb(221, 68, 68); }
.cm-positive { color: rgb(34, 153, 34); }
.cm-header, .cm-strong { font-weight: bold; }
.cm-del { text-decoration: line-through; }
.cm-em { font-style: italic; }
.cm-link { text-decoration: underline; }
.cm-error { color: rgb(255, 0, 0); }
.cm-invalidchar { color: rgb(255, 0, 0); }
.cm-constant { color: rgb(38, 139, 210); }
.cm-defined { color: rgb(181, 137, 0); }
div.CodeMirror span.CodeMirror-matchingbracket { color: rgb(0, 255, 0); }
div.CodeMirror span.CodeMirror-nonmatchingbracket { color: rgb(255, 34, 34); }
.cm-s-inner .CodeMirror-activeline-background { background: inherit; }
.CodeMirror { position: relative; overflow: hidden; }
.CodeMirror-scroll { margin-bottom: -30px; margin-right: -30px; padding-bottom: 30px; padding-right: 30px; height: 100%; outline: none; position: relative; box-sizing: content-box; }
.CodeMirror-sizer { position: relative; }
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { position: absolute; z-index: 6; display: none; }
.CodeMirror-vscrollbar { right: 0px; top: 0px; overflow-x: hidden; overflow-y: scroll; }
.CodeMirror-hscrollbar { bottom: 0px; left: 0px; overflow-y: hidden; overflow-x: scroll; }
.CodeMirror-scrollbar-filler { right: 0px; bottom: 0px; }
.CodeMirror-gutter-filler { left: 0px; bottom: 0px; }
.CodeMirror-gutters { position: absolute; left: 0px; top: 0px; padding-bottom: 30px; z-index: 3; }
.CodeMirror-gutter { white-space: normal; height: 100%; box-sizing: content-box; padding-bottom: 30px; margin-bottom: -32px; display: inline-block; }
.CodeMirror-gutter-elt { position: absolute; cursor: default; z-index: 4; }
.CodeMirror-lines { cursor: text; }
.CodeMirror pre { border-radius: 0px; border-width: 0px; background: transparent; font-family: inherit; font-size: inherit; margin: 0px; white-space: pre; word-wrap: normal; color: inherit; z-index: 2; position: relative; overflow: visible; }
.CodeMirror-wrap pre { word-wrap: break-word; white-space: pre-wrap; word-break: normal; }
.CodeMirror-code pre { border-right: 30px solid transparent; width: fit-content; }
.CodeMirror-wrap .CodeMirror-code pre { border-right: none; width: auto; }
.CodeMirror-linebackground { position: absolute; left: 0px; right: 0px; top: 0px; bottom: 0px; z-index: 0; }
.CodeMirror-linewidget { position: relative; z-index: 2; overflow: auto; }
.CodeMirror-widget { }
.CodeMirror-wrap .CodeMirror-scroll { overflow-x: hidden; }
.CodeMirror-measure { position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden; }
.CodeMirror-measure pre { position: static; }
.CodeMirror div.CodeMirror-cursor { position: absolute; visibility: hidden; border-right: none; width: 0px; }
.CodeMirror div.CodeMirror-cursor { visibility: hidden; }
.CodeMirror-focused div.CodeMirror-cursor { visibility: inherit; }
.CodeMirror-selected { background: rgb(217, 217, 217); }
.CodeMirror-focused .CodeMirror-selected { background: rgb(215, 212, 240); }
.cm-searching { background: rgba(255, 255, 0, 0.4); }
.CodeMirror span { }
@media print { 
  .CodeMirror div.CodeMirror-cursor { visibility: hidden; }
}
.CodeMirror-lint-markers { width: 16px; }
.CodeMirror-lint-tooltip { background-color: infobackground; border: 1px solid black; border-radius: 4px; color: infotext; font-family: monospace; overflow: hidden; padding: 2px 5px; position: fixed; white-space: pre-wrap; z-index: 10000; max-width: 600px; opacity: 0; transition: opacity 0.4s; font-size: 0.8em; }
.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning { background-position: left bottom; background-repeat: repeat-x; }
.CodeMirror-lint-mark-error { background-image: url(""); }
.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning { background-position: center center; background-repeat: no-repeat; cursor: pointer; display: inline-block; height: 16px; width: 16px; vertical-align: middle; position: relative; }
.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning { padding-left: 18px; background-position: left top; background-repeat: no-repeat; }
.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error { background-image: url(""); }
.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning { background-image: url(""); }
.CodeMirror-lint-marker-multiple { background-image: url(""); background-repeat: no-repeat; background-position: right bottom; width: 100%; height: 100%; }


html { font-size: 14px; background-color: rgb(255, 255, 255); color: rgb(51, 51, 51); }
body { margin: 0px; padding: 0px; height: auto; bottom: 0px; top: 0px; left: 0px; right: 0px; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 1rem; line-height: 1.42857; overflow-x: hidden; background: inherit; }
a:active, a:hover { outline: 0px; }
.in-text-selection, ::selection { background: rgb(181, 214, 252); text-shadow: none; }
#write { margin: 0px auto; height: auto; width: inherit; word-break: normal; word-wrap: break-word; position: relative; padding-bottom: 70px; white-space: pre-wrap; overflow-x: auto; }
.for-image #write { padding-left: 8px; padding-right: 8px; }
body.typora-export { padding-left: 30px; padding-right: 30px; }
@media screen and (max-width: 500px) { 
  body.typora-export { padding-left: 0px; padding-right: 0px; }
  .CodeMirror-sizer { margin-left: 0px !important; }
  .CodeMirror-gutters { display: none !important; }
}
.typora-export #write { margin: 0px auto; }
#write > p:first-child, #write > ul:first-child, #write > ol:first-child, #write > pre:first-child, #write > blockquote:first-child, #write > div:first-child, #write > table:first-child { margin-top: 30px; }
#write li > table:first-child { margin-top: -20px; }
img { max-width: 100%; vertical-align: middle; }
input, button, select, textarea { color: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; font-size: inherit; line-height: inherit; font-family: inherit; }
input[type="checkbox"], input[type="radio"] { line-height: normal; padding: 0px; }
::before, ::after, * { box-sizing: border-box; }
#write p, #write h1, #write h2, #write h3, #write h4, #write h5, #write h6, #write div, #write pre { width: inherit; }
#write p, #write h1, #write h2, #write h3, #write h4, #write h5, #write h6 { position: relative; }
h1 { font-size: 2rem; }
h2 { font-size: 1.8rem; }
h3 { font-size: 1.6rem; }
h4 { font-size: 1.4rem; }
h5 { font-size: 1.2rem; }
h6 { font-size: 1rem; }
p { -webkit-margin-before: 1rem; -webkit-margin-after: 1rem; -webkit-margin-start: 0px; -webkit-margin-end: 0px; }
.mathjax-block { margin-top: 0px; margin-bottom: 0px; -webkit-margin-before: 0rem; -webkit-margin-after: 0rem; }
.hidden { display: none; }
.md-blockmeta { color: rgb(204, 204, 204); font-weight: bold; font-style: italic; }
a { cursor: pointer; }
#write input[type="checkbox"] { cursor: pointer; width: inherit; height: inherit; margin: 4px 0px 0px; }
tr { break-inside: avoid; break-after: auto; }
thead { display: table-header-group; }
table { border-collapse: collapse; border-spacing: 0px; width: 100%; overflow: auto; break-inside: auto; text-align: left; }
table.md-table td { min-width: 80px; }
.CodeMirror-gutters { border-right: 0px; background-color: inherit; }
.CodeMirror { text-align: left; }
.CodeMirror-placeholder { opacity: 0.3; }
.CodeMirror pre { padding: 0px 4px; }
.CodeMirror-lines { padding: 0px; }
div.hr:focus { cursor: none; }
pre { white-space: pre-wrap; }
.CodeMirror-gutters { margin-right: 4px; }
.md-fences { font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; overflow: visible; white-space: pre; background: inherit; position: relative !important; }
.md-diagram-panel { width: 100%; margin-top: 10px; text-align: center; padding-top: 0px; padding-bottom: 8px; overflow-x: auto; }
.md-fences .CodeMirror.CodeMirror-wrap { top: -1.6em; margin-bottom: -1.6em; }
.md-fences.mock-cm { white-space: pre-wrap; }
.show-fences-line-number .md-fences { padding-left: 0px; }
.show-fences-line-number .md-fences.mock-cm { padding-left: 40px; }
.footnotes { opacity: 0.8; font-size: 0.9rem; padding-top: 1em; padding-bottom: 1em; }
.footnotes + .footnotes { margin-top: -1em; }
.md-reset { margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: top; background: transparent; text-decoration: none; text-shadow: none; float: none; position: static; width: auto; height: auto; white-space: nowrap; cursor: inherit; -webkit-tap-highlight-color: transparent; line-height: normal; font-weight: normal; text-align: left; box-sizing: content-box; direction: ltr; }
li div { padding-top: 0px; }
blockquote { margin: 1rem 0px; }
li p, li .mathjax-block { margin: 0.5rem 0px; }
li { margin: 0px; position: relative; }
blockquote > :last-child { margin-bottom: 0px; }
blockquote > :first-child { margin-top: 0px; }
.footnotes-area { color: rgb(136, 136, 136); margin-top: 0.714rem; padding-bottom: 0.143rem; }
@media print { 
  html, body { height: 100%; }
  .typora-export * { -webkit-print-color-adjust: exact; }
  h1, h2, h3, h4, h5, h6 { break-after: avoid-page; orphans: 2; }
  p { orphans: 4; }
  html.blink-to-pdf { font-size: 13px; }
  .typora-export #write { padding-left: 1cm; padding-right: 1cm; }
  .typora-export #write::after { height: 0px; }
  @page { margin: 20mm 0mm; }
}
.footnote-line { margin-top: 0.714em; font-size: 0.7em; }
a img, img a { cursor: pointer; }
pre.md-meta-block { font-size: 0.8rem; min-height: 2.86rem; white-space: pre-wrap; background: rgb(204, 204, 204); display: block; overflow-x: hidden; }
p .md-image:only-child { display: inline-block; width: 100%; text-align: center; }
#write .MathJax_Display { margin: 0.8em 0px 0px; }
.mathjax-block { white-space: pre; overflow: hidden; width: 100%; }
p + .mathjax-block { margin-top: -1.143rem; }
.mathjax-block:not(:empty)::after { display: none; }
[contenteditable="true"]:active, [contenteditable="true"]:focus { outline: none; box-shadow: none; }
.task-list { list-style-type: none; }
.task-list-item { position: relative; padding-left: 1em; }
.task-list-item input { position: absolute; top: 0px; left: 0px; }
.math { font-size: 1rem; }
.md-toc { min-height: 3.58rem; position: relative; font-size: 0.9rem; border-radius: 10px; }
.md-toc-content { position: relative; margin-left: 0px; }
.md-toc::after, .md-toc-content::after { display: none; }
.md-toc-item { display: block; color: rgb(65, 131, 196); text-decoration: none; }
.md-toc-inner:hover { }
.md-toc-inner { display: inline-block; cursor: pointer; }
.md-toc-h1 .md-toc-inner { margin-left: 0px; font-weight: bold; }
.md-toc-h2 .md-toc-inner { margin-left: 2em; }
.md-toc-h3 .md-toc-inner { margin-left: 4em; }
.md-toc-h4 .md-toc-inner { margin-left: 6em; }
.md-toc-h5 .md-toc-inner { margin-left: 8em; }
.md-toc-h6 .md-toc-inner { margin-left: 10em; }
@media screen and (max-width: 48em) { 
  .md-toc-h3 .md-toc-inner { margin-left: 3.5em; }
  .md-toc-h4 .md-toc-inner { margin-left: 5em; }
  .md-toc-h5 .md-toc-inner { margin-left: 6.5em; }
  .md-toc-h6 .md-toc-inner { margin-left: 8em; }
}
a.md-toc-inner { font-size: inherit; font-style: inherit; font-weight: inherit; line-height: inherit; }
.footnote-line a:not(.reversefootnote) { color: inherit; }
.md-attr { display: none; }
.md-fn-count::after { content: "."; }
.md-tag { opacity: 0.5; }
.md-comment { color: rgb(162, 127, 3); opacity: 0.8; font-family: monospace; }
code { text-align: left; }
h1 .md-tag, h2 .md-tag, h3 .md-tag, h4 .md-tag, h5 .md-tag, h6 .md-tag { font-weight: initial; opacity: 0.35; }
a.md-print-anchor { border-width: initial !important; border-style: none !important; border-color: initial !important; display: inline-block !important; position: absolute !important; width: 1px !important; right: 0px !important; outline: none !important; background: transparent !important; text-decoration: initial !important; text-shadow: initial !important; }
.md-inline-math .MathJax_SVG .noError { display: none !important; }
.mathjax-block .MathJax_SVG_Display { text-align: center; margin: 1em 0em; position: relative; text-indent: 0px; max-width: none; max-height: none; min-height: 0px; min-width: 100%; width: auto; display: block !important; }
.MathJax_SVG_Display, .md-inline-math .MathJax_SVG_Display { width: auto; margin: inherit; display: inline-block !important; }
.MathJax_SVG .MJX-monospace { font-family: monospace; }
.MathJax_SVG .MJX-sans-serif { font-family: sans-serif; }
.MathJax_SVG { display: inline; font-style: normal; font-weight: normal; line-height: normal; zoom: 90%; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; word-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; padding: 0px; margin: 0px; }
.MathJax_SVG * { transition: none; }


@font-face { font-family: "Open Sans"; font-style: normal; font-weight: normal; src: local("Open Sans Regular"), url("./github/400.woff") format("woff"); }
@font-face { font-family: "Open Sans"; font-style: italic; font-weight: normal; src: local("Open Sans Italic"), url("./github/400i.woff") format("woff"); }
@font-face { font-family: "Open Sans"; font-style: normal; font-weight: bold; src: local("Open Sans Bold"), url("./github/700.woff") format("woff"); }
@font-face { font-family: "Open Sans"; font-style: italic; font-weight: bold; src: local("Open Sans Bold Italic"), url("./github/700i.woff") format("woff"); }
html { font-size: 16px; }
body { font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; color: rgb(51, 51, 51); line-height: 1.6; }
#write { max-width: 860px; margin: 0px auto; padding: 20px 30px 100px; }
#write > ul:first-child, #write > ol:first-child { margin-top: 30px; }
body > :first-child { margin-top: 0px !important; }
body > :last-child { margin-bottom: 0px !important; }
a { color: rgb(65, 131, 196); }
h1, h2, h3, h4, h5, h6 { position: relative; margin-top: 1rem; margin-bottom: 1rem; font-weight: bold; line-height: 1.4; cursor: text; }
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor { text-decoration: none; }
h1 tt, h1 code { font-size: inherit; }
h2 tt, h2 code { font-size: inherit; }
h3 tt, h3 code { font-size: inherit; }
h4 tt, h4 code { font-size: inherit; }
h5 tt, h5 code { font-size: inherit; }
h6 tt, h6 code { font-size: inherit; }
h1 { padding-bottom: 0.3em; font-size: 2.25em; line-height: 1.2; border-bottom: 1px solid rgb(238, 238, 238); }
h2 { padding-bottom: 0.3em; font-size: 1.75em; line-height: 1.225; border-bottom: 1px solid rgb(238, 238, 238); }
h3 { font-size: 1.5em; line-height: 1.43; }
h4 { font-size: 1.25em; }
h5 { font-size: 1em; }
h6 { font-size: 1em; color: rgb(119, 119, 119); }
p, blockquote, ul, ol, dl, table { margin: 0.8em 0px; }
li > ol, li > ul { margin: 0px; }
hr { height: 4px; padding: 0px; margin: 16px 0px; background-color: rgb(231, 231, 231); border-width: 0px 0px 1px; border-style: none none solid; border-top-color: initial; border-right-color: initial; border-left-color: initial; border-image: initial; overflow: hidden; box-sizing: content-box; border-bottom-color: rgb(221, 221, 221); }
body > h2:first-child { margin-top: 0px; padding-top: 0px; }
body > h1:first-child { margin-top: 0px; padding-top: 0px; }
body > h1:first-child + h2 { margin-top: 0px; padding-top: 0px; }
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child { margin-top: 0px; padding-top: 0px; }
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 { margin-top: 0px; padding-top: 0px; }
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p { margin-top: 0px; }
li p.first { display: inline-block; }
ul, ol { padding-left: 30px; }
ul:first-child, ol:first-child { margin-top: 0px; }
ul:last-child, ol:last-child { margin-bottom: 0px; }
blockquote { border-left: 4px solid rgb(221, 221, 221); padding: 0px 15px; color: rgb(119, 119, 119); }
blockquote blockquote { padding-right: 0px; }
table { padding: 0px; word-break: initial; }
#write { overflow-x: auto; }
table tr { border-top: 1px solid rgb(204, 204, 204); background-color: white; margin: 0px; padding: 0px; }
table tr:nth-child(2n) { background-color: rgb(248, 248, 248); }
table tr th { font-weight: bold; border: 1px solid rgb(204, 204, 204); text-align: left; margin: 0px; padding: 6px 13px; }
table tr td { border: 1px solid rgb(204, 204, 204); text-align: left; margin: 0px; padding: 6px 13px; }
table tr th:first-child, table tr td:first-child { margin-top: 0px; }
table tr th:last-child, table tr td:last-child { margin-bottom: 0px; }
.CodeMirror-gutters { border-right: 1px solid rgb(221, 221, 221); }
.md-fences, code, tt { border: 1px solid rgb(221, 221, 221); background-color: rgb(248, 248, 248); border-radius: 3px; font-family: Consolas, "Liberation Mono", Courier, monospace; padding: 2px 4px 0px; font-size: 0.9em; }
.md-fences { margin-bottom: 15px; margin-top: 15px; padding: 8px 1em 6px; }
.task-list { padding-left: 0px; }
.task-list-item { padding-left: 32px; }
.task-list-item input { top: 3px; left: 8px; }
@media screen and (min-width: 914px) { 
}
@media print { 
  html { font-size: 13px; }
  table, pre { break-inside: avoid; }
  pre { word-wrap: break-word; }
}
.md-fences { background-color: rgb(248, 248, 248); }
#write pre.md-meta-block { padding: 1rem; font-size: 85%; line-height: 1.45; background-color: rgb(247, 247, 247); border: 0px; border-radius: 3px; color: rgb(119, 119, 119); margin-top: 0px !important; }
.mathjax-block > .code-tooltip { bottom: 0.375rem; }
#write > h3.md-focus::before { left: -1.5625rem; top: 0.375rem; }
#write > h4.md-focus::before { left: -1.5625rem; top: 0.285714rem; }
#write > h5.md-focus::before { left: -1.5625rem; top: 0.285714rem; }
#write > h6.md-focus::before { left: -1.5625rem; top: 0.285714rem; }
.md-image > .md-meta { border: 1px solid rgb(221, 221, 221); background-color: rgb(248, 248, 248); border-radius: 3px; font-family: Consolas, "Liberation Mono", Courier, monospace; padding: 2px 4px 0px; font-size: 0.9em; color: inherit; }
.md-tag { color: inherit; }
.md-toc { margin-top: 20px; padding-bottom: 20px; }
#typora-quick-open { border: 1px solid rgb(221, 221, 221); background-color: rgb(248, 248, 248); }
#typora-quick-open-item { background-color: rgb(250, 250, 250); border-color: rgb(254, 254, 254) rgb(229, 229, 229) rgb(229, 229, 229) rgb(238, 238, 238); border-style: solid; border-width: 1px; }
#md-notification::before { top: 10px; }
.on-focus-mode blockquote { border-left-color: rgba(85, 85, 85, 0.117647); }
header, .context-menu, .megamenu-content, footer { font-family: "Segoe UI", Arial, sans-serif; }






</style>
</head>
<body class='typora-export' >
<div  id='write'  class = 'is-node'><div class='md-toc' mdtype='toc'><p class="md-toc-content"><span class="md-toc-item md-toc-h2" data-ref="c6"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c6">thinkphp-queue 笔记</a></span><span class="md-toc-item md-toc-h3" data-ref="c7"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c7">前言</a></span><span class="md-toc-item md-toc-h3" data-ref="c62"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c62">一 代码示例</a></span><span class="md-toc-item md-toc-h4" data-ref="c70"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c70">1.1 安装 thinkphp-queue</a></span><span class="md-toc-item md-toc-h4" data-ref="c72"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c72">1.2 搭建消息队列的存储环境</a></span><span class="md-toc-item md-toc-h4" data-ref="c82"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c82">1.3 配置消息队列的驱动</a></span><span class="md-toc-item md-toc-h4" data-ref="c110"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c110">1.4 消息的创建与推送</a></span><span class="md-toc-item md-toc-h4" data-ref="c121"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c121">1.5 消息的消费与删除</a></span><span class="md-toc-item md-toc-h4" data-ref="c131"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c131">1.6 发布任务</a></span><span class="md-toc-item md-toc-h4" data-ref="c136"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c136">1.7 处理任务</a></span><span class="md-toc-item md-toc-h3" data-ref="c152"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c152">二 详细介绍</a></span><span class="md-toc-item md-toc-h4" data-ref="c153"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c153">2.1 命令模式</a></span><span class="md-toc-item md-toc-h4" data-ref="c170"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c170">2.2 命令行参数</a></span><span class="md-toc-item md-toc-h4" data-ref="c182"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c182">2.3 work 模式和 listen 模式的区别</a></span><span class="md-toc-item md-toc-h4" data-ref="c300"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c300">2.4 消息队列的开始，停止与重启</a></span><span class="md-toc-item md-toc-h4" data-ref="c316"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c316">2.5 多模块，多任务的处理</a></span><span class="md-toc-item md-toc-h4" data-ref="c346"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c346">2.6 消息的延迟执行与定时执行</a></span><span class="md-toc-item md-toc-h4" data-ref="c368"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c368">2.7 消息的重发</a></span><span class="md-toc-item md-toc-h4" data-ref="c408"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c408">2.8 任务的失败回调及告警</a></span><span class="md-toc-item md-toc-h4" data-ref="c449"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c449">2.9 处理过期的任务</a></span><span class="md-toc-item md-toc-h3" data-ref="c452"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c452">三 深入理解</a></span><span class="md-toc-item md-toc-h4" data-ref="c453"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c453">3.1 thinkphp-queue 中消息与队列的保存方式</a></span><span class="md-toc-item md-toc-h4" data-ref="c505"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c505">3.2 thinkphp-queue 的目录结构和类关系图</a></span><span class="md-toc-item md-toc-h4" data-ref="c539"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c539">3.3 Deamon模式的执行流程</a></span><span class="md-toc-item md-toc-h4" data-ref="c542"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c542">3.4 Database模式下消息处理的详细流程</a></span><span class="md-toc-item md-toc-h4" data-ref="c547"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c547">3.5 redis 驱动下的任务重发细节</a></span><span class="md-toc-item md-toc-h4" data-ref="c572"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c572">3.6 thinkphp-queue的性能</a></span><span class="md-toc-item md-toc-h4" data-ref="c594"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c594">3.7 thinkphp-queue 的N种错误使用姿势</a></span><span class="md-toc-item md-toc-h3" data-ref="c668"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c668">四 拓展</a></span><span class="md-toc-item md-toc-h4" data-ref="c669"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c669">4.1 队列的稳定性和拓展性</a></span><span class="md-toc-item md-toc-h4" data-ref="c677"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c677">4.2 消息队列的可视化管理工具</a></span><span class="md-toc-item md-toc-h4" data-ref="c685"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c685">4.2 编写自定义的 thinkphp-queue 驱动</a></span><span class="md-toc-item md-toc-h4" data-ref="c688"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c688">4.3 编写消息队列的单元测试</a></span><span class="md-toc-item md-toc-h4" data-ref="c691"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c691">4.4 与其他PHP消息队列库的对比</a></span><span class="md-toc-item md-toc-h3" data-ref="c755"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c755">五 待讨论的问题</a></span><span class="md-toc-item md-toc-h4" data-ref="c756"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c756">5.1 thinkphp-queue 中消息名称与消费者类名的绑定怎么实现？</a></span><span class="md-toc-item md-toc-h4" data-ref="c757"><a class="md-toc-inner" style="cursor: pointer;" href="#header-c757">5.1 消息中的业务数据的格式如何约定？</a></span></p></div><h2><a name='header-c6' class='md-header-anchor '></a>thinkphp-queue 笔记</h2><h3><a name='header-c7' class='md-header-anchor '></a>前言</h3><p>传统的程序执行流程一般是 即时|同步|串行的，在某些场景下，会存在并发低，吞吐量低，响应时间长等问题。在大型系统中，一般会引入消息队列的组件，将流程中部分任务抽离出来放入消息队列，并由专门的消费者作针对性的处理，从而降低系统耦合度，提高系统性能和可用性。</p><p>一般来说，可以抽离的任务具有以下的特点：</p><ul><li><p><strong>允许延后|异步|并行处理</strong> （相对于传统的 <strong>即时|同步|串行</strong> 的执行方式）</p><ul><li><p><strong>允许延后</strong>：</p><p>抢购活动时，先快速缓冲有限的参与人数到消息队列，后续再排队处理实际的抢购业务；</p></li><li><p><strong>允许异步</strong>：</p><p>业务处理过程中的邮件，短信等通知</p></li><li><p><strong>允许并行</strong>：</p><p>用户支付成功之后，邮件通知，微信通知，短信通知可以由多个不同的消费者并行执行，通知到达的时间不要求先后顺序。</p></li></ul></li><li><p><strong>允许失败和重试</strong><br/></p><ul><li>强一致性的业务放入核心流程处理</li><li>无一致性要求或最终一致即可的业务放入队列处理</li></ul></li></ul><p><strong><a href='https://github.com/top-think/think-queue/releases'>thinkphp-queue</a></strong> 是thinkphp 官方提供的一个消息队列服务，它支持消息队列的一些基本特性：</p><ul><li>消息的<strong>发布</strong>，<strong>获取</strong>，<strong>执行</strong>，<strong>删除</strong>，<strong>重发</strong>，<strong>失败处理</strong>，<strong>延迟执行</strong>，<strong>超时控制</strong>等</li><li>队列的<strong>多队列</strong>， <strong>内存限制</strong> ，<strong>启动</strong>，<strong>停止</strong>，<strong>守护</strong>等</li><li>消息队列可<strong>降级为同步执行</strong></li></ul><p>thinkphp-queue 内置了 <strong>Redis</strong>，<strong>Database</strong>，<strong>Topthink</strong> ，<strong>Sync</strong>这四种驱动。本文主要介绍 thinkphp-queue 结合其内置的 redis 驱动的使用方式和基本原理。</p><p>注1：如无特殊说明，下文中的 ‘消息’ 和 ‘任务’两个词指代的是同一个概念，即队列中的一个成员。该成员对消息队列而言是其内部保存的消息； 对业务应用而言是一个待执行的任务。请根据语境区分。</p><p>注2：本文编写时(2017-02-15)使用的 thinkphp-queue 的版本号是 v1.1.2 。该版本中部分功能并未全部完成，如 subscribe 模式，以及存在几个bug(稍后会提及)。如有变更，请以官方最新版为准。</p><p></p><h3><a name='header-c62' class='md-header-anchor '></a>一 代码示例</h3><p>先通过一段代码，了解一下 thinkphp-queue 的基本使用流程。</p><blockquote><p>目标： </p><p>在业务控制器中推送一个新消息到一个名为 ‘helloJobQueue’ 的队列中，该消息中包含我们自定义的业务数据，然后，编写一个名为 Hello 的消费者类，并通过命令行去调用该消费者类获取这个消息，拿到定义的数据。</p></blockquote><h4><a name='header-c70' class='md-header-anchor '></a>1.1 安装 thinkphp-queue</h4><pre class="md-fences md-end-block" lang="bash"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure">AخA</div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;">composer install thinkphp-queue</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre><h4><a name='header-c72' class='md-header-anchor '></a>1.2 搭建消息队列的存储环境</h4><ul><li><p>使用 Redis [<strong>推荐</strong>]</p><pre class="md-fences md-end-block" lang="json"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-variable">安装并启动</span> <span class="cm-variable">Redis</span> <span class="cm-variable">服务</span></span></pre></div></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre></li><li><p>使用数据库 [不推荐]</p><pre class="md-fences md-end-block" lang="mysql"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-keyword">CREATE</span> <span class="cm-keyword">TABLE</span> <span class="cm-variable-2">`prefix_jobs`</span> (</span></pre></div><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-variable-2">`id`</span> <span class="cm-builtin">int</span>(<span class="cm-number">11</span>) <span class="cm-keyword">NOT</span> <span class="cm-atom">NULL</span> <span class="cm-keyword">AUTO_INCREMENT</span>,</span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-variable-2">`queue`</span> <span class="cm-builtin">varchar</span>(<span class="cm-number">255</span>) <span class="cm-keyword">NOT</span> <span class="cm-atom">NULL</span>,</span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-variable-2">`payload`</span> <span class="cm-builtin">longtext</span> <span class="cm-keyword">NOT</span> <span class="cm-atom">NULL</span>,</span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-variable-2">`attempts`</span> <span class="cm-builtin">tinyint</span>(<span class="cm-number">3</span>) <span class="cm-builtin">unsigned</span> <span class="cm-keyword">NOT</span> <span class="cm-atom">NULL</span>,</span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-variable-2">`reserved`</span> <span class="cm-builtin">tinyint</span>(<span class="cm-number">3</span>) <span class="cm-builtin">unsigned</span> <span class="cm-keyword">NOT</span> <span class="cm-atom">NULL</span>,</span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-variable-2">`reserved_at`</span> <span class="cm-builtin">int</span>(<span class="cm-number">10</span>) <span class="cm-builtin">unsigned</span> <span class="cm-keyword">DEFAULT</span> <span class="cm-atom">NULL</span>,</span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-variable-2">`available_at`</span> <span class="cm-builtin">int</span>(<span class="cm-number">10</span>) <span class="cm-builtin">unsigned</span> <span class="cm-keyword">NOT</span> <span class="cm-atom">NULL</span>,</span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-variable-2">`created_at`</span> <span class="cm-builtin">int</span>(<span class="cm-number">10</span>) <span class="cm-builtin">unsigned</span> <span class="cm-keyword">NOT</span> <span class="cm-atom">NULL</span>,</span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-keyword">PRIMARY</span> <span class="cm-keyword">KEY</span> (<span class="cm-variable-2">`id`</span>)</span></pre><pre class=""><span style="padding-right: 0.1px;">) <span class="cm-keyword">ENGINE</span>=<span class="cm-keyword">InnoDB</span> <span class="cm-keyword">DEFAULT</span> <span class="cm-string-2">CHARSET</span>=utf8;</span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 253px;"></div></div></div></pre></li></ul><h4><a name='header-c82' class='md-header-anchor '></a>1.3 配置消息队列的驱动</h4><p>   根据选择的存储方式，在 <code>\application\extra\queue.php</code> 这个配置文件中，添加消息队列对应的驱动配置</p><pre class="md-fences md-end-block" lang="php"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><span><span>​</span>x</span></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-keyword">return</span> [</span></pre></div><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-string">'connector'</span>  <span class="cm-operator">=&gt;</span> <span class="cm-string">'Redis'</span>,<span class="cm-tab"> </span><span class="cm-tab">    </span> &nbsp;  <span class="cm-comment">// Redis 驱动</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-string">'expire'</span> &nbsp; &nbsp; <span class="cm-operator">=&gt;</span> <span class="cm-number">60</span>,<span class="cm-tab">  </span><span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-comment">// 任务的过期时间，默认为60秒; 若要禁用，则设置为 null </span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-string">'default'</span> &nbsp;  <span class="cm-operator">=&gt;</span> <span class="cm-string">'default'</span>,<span class="cm-tab">   </span><span class="cm-tab">    </span><span class="cm-comment">// 默认的队列名称</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-string">'host'</span> &nbsp; &nbsp; &nbsp; <span class="cm-operator">=&gt;</span> <span class="cm-string">'127.0.0.1'</span>,<span class="cm-tab"> </span> &nbsp;  <span class="cm-comment">// redis 主机ip</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-string">'port'</span> &nbsp; &nbsp; &nbsp; <span class="cm-operator">=&gt;</span> <span class="cm-number">6379</span>,<span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-comment">// redis 端口</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-string">'password'</span> &nbsp; <span class="cm-operator">=&gt;</span> <span class="cm-string">''</span>,<span class="cm-tab">  </span><span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-comment">// redis 密码</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-string">'select'</span> &nbsp; &nbsp; <span class="cm-operator">=&gt;</span> <span class="cm-number">0</span>,<span class="cm-tab">   </span><span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-comment">// 使用哪一个 db，默认为 db0</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-string">'timeout'</span> &nbsp;  <span class="cm-operator">=&gt;</span> <span class="cm-number">0</span>,<span class="cm-tab">   </span><span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-comment">// redis连接的超时时间</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-string">'persistent'</span> <span class="cm-operator">=&gt;</span> <span class="cm-atom">false</span>,<span class="cm-tab">   </span><span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-comment">// 是否是长连接</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">// &nbsp;  'connector' =&gt; 'Database', &nbsp; // 数据库驱动</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">// &nbsp;  'expire' &nbsp;  =&gt; 60, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 任务的过期时间，默认为60秒; 若要禁用，则设置为 null</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">// &nbsp;  'default' &nbsp; =&gt; 'default', &nbsp;  // 默认的队列名称</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">// &nbsp;  'table' &nbsp; &nbsp; =&gt; 'jobs', &nbsp; &nbsp; &nbsp; // 存储消息的表名，不带前缀</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">// &nbsp;  'dsn' &nbsp; &nbsp; &nbsp; =&gt; [],</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">// &nbsp;  'connector' &nbsp; =&gt; 'Topthink',<span class="cm-tab">   </span>// ThinkPHP内部的队列通知服务平台 ，本文不作介绍</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">// &nbsp;  'token' &nbsp; &nbsp; &nbsp; =&gt; '',</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">// &nbsp;  'project_id'  =&gt; '',</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">// &nbsp;  'protocol' &nbsp;  =&gt; 'https',</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">// &nbsp;  'host' &nbsp; &nbsp; &nbsp;  =&gt; 'qns.topthink.com',</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">// &nbsp;  'port' &nbsp; &nbsp; &nbsp;  =&gt; 443,</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">// &nbsp;  'api_version' =&gt; 1,</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">// &nbsp;  'max_retries' =&gt; 3,</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">// &nbsp;  'default' &nbsp; &nbsp; =&gt; 'default',</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">// &nbsp;  'connector' &nbsp; =&gt; 'Sync',<span class="cm-tab">   </span><span class="cm-tab">    </span>// Sync 驱动，该驱动的实际作用是取消消息队列，还原为同步执行</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; ];</span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 667px;"></div></div></div></pre><p>   <strong>1.3.1 配置文件中的 expire 参数说明</strong></p><p>   expire 参数指的是任务的过期时间。 过期的任务，其准确的定义是</p><ol start='' ><li>任务的状态为执行中</li><li>任务的开始执行的时刻 + expire &gt; 当前时刻 </li></ol><p>expire 不为<code>null</code> 时 ，thinkphp-queue 会在每次获取下一个任务之前检查并重发过期(执行超时)的任务。</p><p>expire 为<code>null</code> 时，thinkphp-queue 不会检查过期的任务，性能相对较高一点。但是需要注意：</p><ul><li>这些执行超时的任务会一直留在消息队列中，需要开发者另行处理(删除或者重发)！</li><li>[ <strong><a href='https://github.com/top-think/think-queue/issues/12'>Bug</a></strong> ]在redis 驱动下，expire 设置为 null 时，无法实现任务的延迟执行!  (Database 驱动下无影响)</li></ul><p><strong>对expire 参数理解或者使用不当时，很容易产生一些bug</strong>，后面会举例提到。</p><h4><a name='header-c110' class='md-header-anchor '></a>1.4 消息的创建与推送</h4><p>我们在业务控制器中创建一个新的消息，并推送到 <code>helloJobQueue</code> 队列</p><p>新增 <code>\application\index\controller\JobTest.php</code> 控制器，在该控制器中添加 <code>actionWithHelloJob</code> 方法</p><pre class="md-fences md-end-block" lang="php"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-operator">&lt;?</span><span class="cm-variable">php</span></span></pre></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">/**</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">* 文件路径： \application\index\controller\JobTest.php</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">* 该控制器的业务代码中借助了thinkphp-queue 库，将一个消息推送到消息队列</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">*/</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-keyword">namespace</span> <span class="cm-variable">application\index\controller</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-keyword">use</span> <span class="cm-variable">think\Exception</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-keyword">use</span> <span class="cm-variable">think\Queue</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-keyword">class</span> <span class="cm-variable">JobTest</span> {</span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-comment">/**</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">* 一个使用了队列的 action</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">*/</span></span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-keyword">public</span> <span class="cm-keyword">function</span> <span class="cm-variable">actionWithHelloJob</span>(){</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  <span class="cm-comment">// 1.当前任务将由哪个类来负责处理。 </span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  <span class="cm-comment">// &nbsp; 当轮到该任务时，系统将生成一个该类的实例，并调用其 fire 方法</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  <span class="cm-variable-2">$jobHandlerClassName</span>  <span class="cm-operator">=</span> <span class="cm-string">'application\index\job\Hello'</span>; </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  <span class="cm-comment">// 2.当前任务归属的队列名称，如果为新队列，会自动创建</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  <span class="cm-variable-2">$jobQueueName</span>  <span class="cm-tab">   </span>  <span class="cm-operator">=</span> <span class="cm-string">"helloJobQueue"</span>; </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  <span class="cm-comment">// 3.当前任务所需的业务数据 . 不能为 resource 类型，其他类型最终将转化为json形式的字符串</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  <span class="cm-comment">// &nbsp; ( jobData 为对象时，需要在先在此处手动序列化，否则只存储其public属性的键值对)</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  <span class="cm-variable-2">$jobData</span> &nbsp; &nbsp; &nbsp; <span class="cm-tab">   </span>  <span class="cm-operator">=</span> [ <span class="cm-string">'ts'</span> <span class="cm-operator">=&gt;</span> <span class="cm-builtin">time</span>(), <span class="cm-string">'bizId'</span> <span class="cm-operator">=&gt;</span> <span class="cm-builtin">uniqid</span>() , <span class="cm-string">'a'</span> <span class="cm-operator">=&gt;</span> <span class="cm-number">1</span> ] ;</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  <span class="cm-comment">// 4.将该任务推送到消息队列，等待对应的消费者去执行</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  <span class="cm-variable-2">$isPushed</span> <span class="cm-operator">=</span> <span class="cm-variable">Queue</span>::<span class="cm-variable">push</span>( <span class="cm-variable-2">$jobHandlerClassName</span> , <span class="cm-variable-2">$jobData</span> , <span class="cm-variable-2">$jobQueueName</span> );<span class="cm-tab">   </span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  <span class="cm-comment">// database 驱动时，返回值为 1|false  ; &nbsp; redis 驱动时，返回值为 随机字符串|false</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  <span class="cm-keyword">if</span>( <span class="cm-variable-2">$isPushed</span> <span class="cm-operator">!==</span> <span class="cm-atom">false</span> ){  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">echo</span> <span class="cm-builtin">date</span>(<span class="cm-string">'Y-m-d H:i:s'</span>) . <span class="cm-string">" a new Hello Job is Pushed to the MQ"</span>.<span class="cm-string">"&lt;br&gt;"</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  }<span class="cm-keyword">else</span>{</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">echo</span> <span class="cm-string">'Oops, something wrong happened.'</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  }</span></pre><pre class=""><span style="padding-right: 0.1px;">  }</span></pre><pre class=""><span style="padding-right: 0.1px;"> }</span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 782px;"></div></div></div></pre><p><strong>注意:</strong> 在这个例子当中，我们是手动指定的 <code>$jobHandlerClassName</code> ，更合理的做法是先定义好消息名称与消费者类名的映射关系，然后由某个可以获取该映射关系的类来推送这个消息。这样，生产者只需要知道消息的名称，而无需指定哪个消费者类来处理。</p><blockquote><p>除了 <code>Queue::push( $jobHandlerClassName , $jobData , $jobQueueName );</code>这种方式之外，还可以直接传入 <code>Queue::push( $jobHandlerObject ,null , $jobQueueName );</code> 这时，需要在 $jobHandlerObject 中定义一个 <code>handle()</code> 方法，消息队列在执行到该任务时会自动反序列化该对象，并调用其 <code>handle()</code>方法。 该方式的缺点是无法传入自定义数据。</p></blockquote><h4><a name='header-c121' class='md-header-anchor '></a>1.5 消息的消费与删除</h4><p>编写 Hello 消费者类，用于处理 <code>helloJobQueue</code> 队列中的任务</p><p>新增 <code>\application\index\job\Hello.php</code> 消费者类，并编写其 <code>fire()</code>  方法</p><pre class="md-fences md-end-block" lang="php"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-operator">&lt;?</span><span class="cm-variable">php</span></span></pre></div><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-comment">/**</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">* 文件路径： \application\index\job\Hello.php</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">* 这是一个消费者类，用于处理 helloJobQueue 队列中的任务</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-comment">*/</span></span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-keyword">namespace</span> <span class="cm-variable">application\index\job</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-keyword">use</span> <span class="cm-variable">think\queue\Job</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-keyword">class</span> <span class="cm-variable">Hello</span> {</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  <span class="cm-comment">/**</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-comment">* fire方法是消息队列默认调用的方法</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-comment">* @param Job &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  $job &nbsp; &nbsp;  当前的任务对象</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-comment">* @param array|mixed &nbsp;  $data &nbsp; &nbsp; 发布任务时自定义的数据</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-comment">*/</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  <span class="cm-keyword">public</span> <span class="cm-keyword">function</span> <span class="cm-variable">fire</span>(<span class="cm-variable">Job</span> <span class="cm-variable-2">$job</span>,<span class="cm-variable-2">$data</span>){</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-variable-2">$isJobDone</span> <span class="cm-operator">=</span> <span class="cm-variable-2">$this</span><span class="cm-operator">-&gt;</span><span class="cm-variable">doHelloJob</span>(<span class="cm-variable-2">$data</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">if</span> (<span class="cm-variable-2">$isJobDone</span>) {</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-comment">//如果任务执行成功， 记得删除任务</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-variable-2">$job</span><span class="cm-operator">-&gt;</span><span class="cm-variable">delete</span>();</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">print</span>(<span class="cm-string">"&lt;info&gt;Hello Job has been done and deleted"</span>.<span class="cm-string">"&lt;/info&gt;\n"</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;  }<span class="cm-keyword">else</span>{</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">if</span> (<span class="cm-variable-2">$job</span><span class="cm-operator">-&gt;</span><span class="cm-variable">attempts</span>() <span class="cm-operator">&gt;</span> <span class="cm-number">3</span>) {</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-comment">//通过这个方法可以检查这个任务已经重试了几次了</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">print</span>(<span class="cm-string">"&lt;warn&gt;Hello Job has been retried more than 3 times!"</span>.<span class="cm-string">"&lt;/warn&gt;\n"</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-tab">  </span><span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-variable-2">$job</span><span class="cm-operator">-&gt;</span><span class="cm-variable">delete</span>();</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-comment">// 也可以重新发布这个任务</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-comment">//print("&lt;info&gt;Hello Job will be availabe again after 2s."."&lt;/info&gt;\n");</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-comment">//$job-&gt;release(2); //$delay为延迟时间，表示该任务延迟2秒后再执行</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  }</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;  }</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  }</span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  <span class="cm-comment">/**</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-comment">* 根据消息中的数据进行实际的业务处理</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-comment">* @param array|mixed &nbsp;  $data &nbsp; &nbsp; 发布任务时自定义的数据</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-comment">* @return boolean &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 任务执行的结果</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-comment">*/</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  <span class="cm-keyword">private</span> <span class="cm-keyword">function</span> <span class="cm-variable">doHelloJob</span>(<span class="cm-variable-2">$data</span>) {</span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-tab">  </span><span class="cm-tab">    </span><span class="cm-comment">// 根据消息中的数据进行实际的业务处理...</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">print</span>(<span class="cm-string">"&lt;info&gt;Hello Job Started. job Data is: "</span>.<span class="cm-builtin">var_export</span>(<span class="cm-variable-2">$data</span>,<span class="cm-atom">true</span>).<span class="cm-string">"&lt;/info&gt; \n"</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">print</span>(<span class="cm-string">"&lt;info&gt;Hello Job is Fired at "</span> . <span class="cm-builtin">date</span>(<span class="cm-string">'Y-m-d H:i:s'</span>) .<span class="cm-string">"&lt;/info&gt; \n"</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">print</span>(<span class="cm-string">"&lt;info&gt;Hello Job is Done!"</span>.<span class="cm-string">"&lt;/info&gt; \n"</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">return</span> <span class="cm-atom">true</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  }</span></pre><pre class=""><span style="padding-right: 0.1px;">  }</span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 1173px;"></div></div></div></pre><p>至此，所有的代码都已准备完毕，在运行消息队列之前，我们先看一下现在的目录结构：</p><p><img src='%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84-%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B.png' alt='目录结构-代码示例' /></p><h4><a name='header-c131' class='md-header-anchor '></a>1.6 发布任务</h4><p>在浏览器中访问  <a href='http://your.project.domain/index/job_test/actionWithHelloJob' target='_blank' >http://your.project.domain/index/job_test/actionWithHelloJob</a> ,可以看到消息推送成功。</p><p><img src='%E6%B5%8F%E8%A7%88%E5%99%A8%E6%8F%90%E7%A4%BA%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81%E7%BB%93%E6%9E%9C.png' alt='浏览器提示消息推送结果' /></p><h4><a name='header-c136' class='md-header-anchor '></a>1.7 处理任务</h4><p>切换当前终端窗口的目录到项目根目录下，执行</p><pre class="md-fences md-end-block" lang="bash"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;">php think queue:work <span class="cm-attribute">--queue</span> helloJobQueue</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre><p>可以看到执行的结果类似如下:</p><p><img src='%E5%91%BD%E4%BB%A4%E8%A1%8C%E6%89%A7%E8%A1%8C%E7%BB%93%E6%9E%9C.png' alt='命令行执行结果' /></p><p>​</p><p></p><p>至此，我们成功地经历了一个消息的 创建 -&gt; 推送 -&gt; 消费 -&gt; 删除  的基本流程</p><p>下文，将介绍 thinkphp-queue 的详细使用方法。如配置介绍，基本原理，各种特殊情况的处理等</p><h3><a name='header-c152' class='md-header-anchor '></a>二 详细介绍</h3><h4><a name='header-c153' class='md-header-anchor '></a>2.1 命令模式</h4><ul><li><p><strong>queue:subscribe 命令</strong> [截至2017-02-15，作者暂未实现该模式，略过]</p></li><li><p><strong>queue:work 命令</strong></p><p>work 命令： 该命令将启动一个 work 进程来处理消息队列。</p><pre class="md-fences md-end-block" lang="bash"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;">php think queue:work <span class="cm-attribute">--queue</span> helloJobQueue</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre></li><li><p><strong>queue:listen 命令</strong></p><p>listen 命令： 该命令将会创建一个 listen 父进程 ，然后由父进程通过 <code>proc_open(‘php think queue:work’)</code> 的方式来创建一个work 子 进程来处理消息队列，且限制该work进程的执行时间。 </p><pre class="md-fences md-end-block" lang="bash"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;">php think queue:listen <span class="cm-attribute">--queue</span> helloJobQueue</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre></li></ul><h4><a name='header-c170' class='md-header-anchor '></a>2.2 命令行参数</h4><ul><li><p>Work 模式</p><pre class="md-fences md-end-block" lang="bash"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;">php think queue:work \</span></pre></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-attribute">--daemon</span> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  //是否循环执行，如果不加该参数，则该命令处理完下一个消息就退出</span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-attribute">--queue</span>  helloJobQueue  //要处理的队列的名称</span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-attribute">--delay</span>  <span class="cm-number">0</span> \ &nbsp; &nbsp; &nbsp;  //如果本次任务执行抛出异常且任务未被删除时，设置其下次执行前延迟多少秒,默认为0</span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-attribute">--force</span>  \ &nbsp; &nbsp; &nbsp; &nbsp;  //系统处于维护状态时是否仍然处理任务，并未找到相关说明</span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-attribute">--memory</span> <span class="cm-number">128</span> \ &nbsp; &nbsp;  //该进程允许使用的内存上限，以 M 为单位</span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-attribute">--sleep</span>  <span class="cm-number">3</span> \ &nbsp; &nbsp; &nbsp;  //如果队列中无任务，则sleep多少秒后重新检查(work<span class="cm-operator">+</span>daemon模式)或者退出(listen或非daemon模式)</span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-attribute">--tries</span>  <span class="cm-number">2</span> &nbsp; &nbsp; &nbsp; &nbsp;  //如果任务已经超过尝试次数上限，则触发‘任务尝试次数超限’事件，默认为0</span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 207px;"></div></div></div></pre></li><li><p>Listen 模式</p><pre class="md-fences md-end-block" lang="bash"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;">php think queue:listen \</span></pre></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-attribute">--queue</span>  helloJobQueue \ &nbsp; //监听的队列的名称</span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-attribute">--delay</span>  <span class="cm-number">0</span> \ &nbsp; &nbsp; &nbsp; &nbsp; //如果本次任务执行抛出异常且任务未被删除时，设置其下次执行前延迟多少秒,默认为0</span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-attribute">--memory</span> <span class="cm-number">128</span> \ &nbsp; &nbsp; &nbsp; //该进程允许使用的内存上限，以 M 为单位</span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-attribute">--sleep</span>  <span class="cm-number">3</span> \ &nbsp; &nbsp; &nbsp; &nbsp; //如果队列中无任务，则多长时间后重新检查，daemon模式下有效</span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-attribute">--tries</span>  <span class="cm-number">0</span> \ &nbsp; &nbsp; &nbsp; &nbsp; //如果任务已经超过重发次数上限，则进入失败处理逻辑，默认为0</span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-attribute">--timeout</span> <span class="cm-number">60</span> &nbsp; &nbsp; &nbsp; &nbsp; //创建的work子进程的允许执行的最长时间，以秒为单位</span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 161px;"></div></div></div></pre><p>可以看到 listen 模式下，不包含 <code>--deamon</code> 参数，原因下面会说明</p></li></ul><h4><a name='header-c182' class='md-header-anchor '></a>2.3 work 模式和 listen 模式的区别</h4><p>两者都可以用于处理消息队列中的任务</p><p>区别在于:</p><ul><li><p><strong>2.3.1 执行原理不同</strong></p><ul><li><p>work 命令是<strong>单进程</strong>的处理模式。</p><p>按照是否设置了 <code>--daemon</code> 参数，work命令又可分为单次执行和循环执行两种模式。</p><ul><li>单次执行：不添加 <code>--daemon</code>参数，该模式下,work进程在处理完下一个消息后直接结束当前进程。当不存在新消息时，会sleep一段时间然后退出。</li><li>循环执行：添加了 <code>--daemon</code>参数，该模式下,work进程会循环地处理队列中的消息，直到内存超出参数配置才结束进程。当不存在新消息时，会在每次循环中sleep一段时间。</li></ul></li><li><p>listen 命令是 <strong>父进程 + 子进程</strong> 的处理模式。</p><p>listen命令所在的父进程会创建一个<strong>单次执行模式的work子进程</strong>，并通过该work子进程来处理队列中的下一个消息，当这个work子进程退出之后，listen命令所在的父进程会监听到该子进程的退出信号，并重新创建一个新的<strong>单次执行的work子进程</strong></p></li></ul></li><li><p><strong>2.3.2 退出时机不同</strong></p><ul><li><p>work 命令的退出时机在上面的执行原理部分已叙述，此处不再重复</p></li><li><p>listen 命令中，listen所在的父进程正常情况会一直运行，除非遇到下面两种情况：</p><ul><li>创建的某个work子进程的执行时间超过了 listen命令行中的<code>--timeout</code> 参数配置，此时work子进程会被强制结束，listen所在的父进程也会抛出一个 <code>ProcessTimeoutException</code> 异常并退出。开发者可以选择捕获该异常，让父进程继续执行，也可以选择通过 supervisor 等监控软件重启一个新的listen命令。</li><li>listen 命令所在的父进程因某种原因存在内存泄露，则当父进程本身占用的内存超过了命令行中的 <code>--memory</code> 参数配置时，父子进程均会退出。正常情况下，listen进程本身占用的内存是稳定不变的。</li></ul></li></ul></li><li><p><strong>2.3.3 性能不同</strong></p><ul><li><p>work 命令是在脚本内部做循环，框架脚本在命令执行的初期就已加载完毕；</p></li><li><p>而listen模式则是处理完一个任务之后新开一个work进程，此时会重新加载框架脚本。</p><p>因此： <strong>work 模式的性能会比listen模式高</strong>。</p><p>注意：当代码有更新时，work 模式下需要手动去执行 <code>php think queue:restart</code> 命令重启队列来使改动生效；而listen 模式会自动生效,无需其他操作。</p></li></ul></li><li><p><strong>2.3.4 超时控制能力</strong></p><ul><li><p>work 模式本质上既不能控制进程自身的运行时间，也无法限制执行中的任务的执行时间。</p><p>举例来说，假如你在某次上线之后，在上文中的  <code>\application\index\job\Hello.php</code> 消费者的<code>fire</code>方法中添加了一段死循环 ：</p><pre class="md-fences md-end-block" lang="php"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-keyword">public</span> <span class="cm-keyword">function</span> <span class="cm-variable">fire</span>(){</span></pre></div><pre class=""><span style="padding-right: 0.1px;"> &nbsp; <span class="cm-keyword">while</span>(<span class="cm-atom">true</span>){ <span class="cm-comment">//死循环</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-variable-2">$consoleOutPut</span><span class="cm-operator">-&gt;</span><span class="cm-variable">writeln</span>(<span class="cm-string">"&lt;info&gt;I am looping forever inside a job.&lt;/info&gt; \n"</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-builtin">sleep</span>(<span class="cm-number">1</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; }</span></pre><pre class=""><span style="padding-right: 0.1px;">}  </span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 138px;"></div></div></div></pre><p>那么这个循环将永远不能停止，直到任务所在的进程超过内存限制或者由管理员手动结束。这个过程不会有任何的告警。更严重的是，如果你配置了expire ，那么这个死循环的任务可能会污染到同样处理 <code>helloJobQueue</code> 队列的其他work进程，最后好几个work进程将被卡死在这段死循环中。详情后文会说明。</p><p><strong>work 模式下的超时控制能力，实际上应该理解为 多个work 进程配合下的过期任务重发能力。</strong></p></li><li><p>而 listen命令可以限制其创建的work子进程的超时时间。</p><p>listen 命令可通过 <code>--timeout</code> 参数限制work子进程允许运行的最长时间，超过该时间限制仍未结束的子进程会被强制结束；</p></li><li><p>这里有必要补充一下 expire 和 timeout 之间的区别：</p><ul><li>expire 在配置文件中设置，timeout 在 listen命令 的命令行参数中设置，而且，expire 和 timeout 是两个不同层次上的概念：</li></ul></li></ul></li></ul><pre class='md-fences mock-cm' style='display:block;position:relative'>- expire 是指任务的过期时间。这个时间是全局的，影响到所有的work进程。(不管是独立的work命令还是 listen 模式下创建的的work子进程) 。expire 针对的对象是 **任务**。
- timeout 是指work子进程的超时时间。这个时间只对当前执行的listen 命令有效。timeout 针对的对象是 **work子进程**。</pre><ul><li><p><strong>2.3.5 使用场景不同</strong></p><p>根据上面的介绍，可以看到，</p><p>work 命令的适用场景是：</p><ul><li>任务数量较多</li><li>性能要求较高</li><li>任务的执行时间较短</li><li>消费者类中不存在死循环，sleep() ，exit() ,die() 等容易导致bug的逻辑</li></ul><p>listen命令的适用场景是：</p><ul><li>任务数量较少</li><li>任务的执行时间较长(如生成大型的excel报表等)，</li><li>任务的执行时间需要有严格限制</li></ul></li></ul><h4><a name='header-c300' class='md-header-anchor '></a>2.4 消息队列的开始，停止与重启</h4><ul><li><p>开始一个消息队列：</p><pre class="md-fences md-end-block" lang="bash"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;">php think queue:work</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre></li></ul><ul><li><p>停止所有的消息队列：</p><pre class="md-fences md-end-block" lang="bash"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;">php think queue:restart</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre></li><li><p>重启所有的消息队列： </p><pre class="md-fences md-end-block" lang="bash"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;">php think queue:restart </span></pre></div><pre class=""><span style="padding-right: 0.1px;">php think queue:work </span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 46px;"></div></div></div></pre></li></ul><h4><a name='header-c316' class='md-header-anchor '></a>2.5 多模块，多任务的处理</h4><ul><li><p>多模块</p><blockquote><p>单模块项目推荐使用 <code>app\job</code> 作为任务类的命名空间 </p><p>多模块项目可用使用 <code>app\module\job</code> 作为任务类的命名空间 也可以放在任意可以自动加载到的地方</p></blockquote></li><li><p>多任务</p><blockquote><p>如果一个任务类里有多个小任务的话，在发布任务时，需要用 <code>任务的类名@方法名</code> 如 <code>app\lib\job\Job2@task1</code>、<code>app\lib\job\Job2@task2</code></p><p>注意：命令行中的 --queue  参数不支持@解析</p></blockquote><p>多任务例子:</p><ul><li>在 <code>\application\index\controller\JobTest.php</code> 控制器中，添加 <code>actionWithMultiTask()</code>方法：</li></ul><pre class="md-fences md-end-block" lang="php"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-keyword">public</span> <span class="cm-keyword">function</span> <span class="cm-variable">actionWithMultiTask</span>(){</span></pre></div><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-tab">    </span></span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-variable-2">$taskType</span> <span class="cm-operator">=</span> <span class="cm-variable-2">$_GET</span>[<span class="cm-string">'taskType'</span>];</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-keyword">switch</span> (<span class="cm-variable-2">$whichTask</span>) {</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-keyword">case</span> <span class="cm-string">'taskA'</span>:</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-variable-2">$jobHandlerClassName</span>  <span class="cm-operator">=</span> <span class="cm-string">'application\index\job\MultiTask@taskA'</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-variable-2">$jobDataArr</span> <span class="cm-operator">=</span> [<span class="cm-string">'a'</span><span class="cm-tab">   </span><span class="cm-operator">=&gt;</span> <span class="cm-string">'1'</span>];</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-variable-2">$jobQueueName</span> <span class="cm-operator">=</span> <span class="cm-string">"multiTaskJobQueue"</span>;<span class="cm-tab"> </span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-keyword">break</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; <span class="cm-keyword">case</span> <span class="cm-string">'taskB'</span>:</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-variable-2">$jobHandlerClassName</span>  <span class="cm-operator">=</span> <span class="cm-string">'application\index\job\MultiTask@taskB'</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-variable-2">$jobDataArr</span> <span class="cm-operator">=</span> [<span class="cm-string">'b'</span><span class="cm-tab">   </span><span class="cm-operator">=&gt;</span> <span class="cm-string">'2'</span>];</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-variable-2">$jobQueueName</span> <span class="cm-operator">=</span> <span class="cm-string">"multiTaskJobQueue"</span>;<span class="cm-tab"> </span><span class="cm-tab">    </span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-keyword">break</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">default</span>:</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-keyword">break</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; }</span></pre><pre class=""><span style="padding-right: 0.1px;">  </span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-variable-2">$isPushed</span> <span class="cm-operator">=</span> <span class="cm-variable">Queue</span>::<span class="cm-variable">push</span>(<span class="cm-variable-2">$jobHandlerClassName</span>, <span class="cm-variable-2">$jobDataArr</span>, <span class="cm-variable-2">$jobQueueName</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-keyword">if</span> (<span class="cm-variable-2">$isPushed</span> <span class="cm-operator">!==</span> <span class="cm-atom">false</span>) {</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-keyword">echo</span>(<span class="cm-string">"the $taskType of MultiTask Job has been Pushed to "</span>.<span class="cm-variable-2">$jobQueueName</span> .<span class="cm-string">"&lt;br&gt;"</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;">  }<span class="cm-keyword">else</span>{</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-keyword">throw</span> <span class="cm-keyword">new</span> <span class="cm-variable">Exception</span>(<span class="cm-string">"push a new $taskType of MultiTask Job Failed!"</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;">  }</span></pre><pre class=""><span style="padding-right: 0.1px;">}</span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 575px;"></div></div></div></pre><ul><li>新增 <code>\application\index\job\MultiTask.php</code> 消费者类，并编写其 <code>taskA()</code>  和 <code>taskB()</code>方法</li></ul><pre class="md-fences md-end-block" lang="php"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-operator">&lt;?</span><span class="cm-variable">php</span></span></pre></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">/**</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-comment">* 文件路径： \application\index\job\MultiTask.php</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-comment">* 这是一个消费者类，用于处理 multiTaskJobQueue 队列中的任务</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-comment">*/</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-keyword">namespace</span> <span class="cm-variable">application\index\job</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-keyword">use</span> <span class="cm-variable">think\queue\Job</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-keyword">class</span> <span class="cm-variable">MultiTask</span> {</span></pre><pre class=""><span style="padding-right: 0.1px;"> </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-keyword">public</span> <span class="cm-keyword">function</span> <span class="cm-variable">taskA</span>(<span class="cm-variable">Job</span> <span class="cm-variable-2">$job</span>,<span class="cm-variable-2">$data</span>){</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  <span class="cm-variable-2">$isJobDone</span> <span class="cm-operator">=</span> <span class="cm-variable-2">$this</span><span class="cm-operator">-&gt;</span><span class="cm-variable">_doTaskA</span>(<span class="cm-variable-2">$data</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">if</span> (<span class="cm-variable-2">$isJobDone</span>) {</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-variable-2">$job</span><span class="cm-operator">-&gt;</span><span class="cm-variable">delete</span>();</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">print</span>(<span class="cm-string">"Info: TaskA of Job MultiTask has been done and deleted"</span>.<span class="cm-string">"\n"</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  }<span class="cm-keyword">else</span>{</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">if</span> (<span class="cm-variable-2">$job</span><span class="cm-operator">-&gt;</span><span class="cm-variable">attempts</span>() <span class="cm-operator">&gt;</span> <span class="cm-number">3</span>) {</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-variable-2">$job</span><span class="cm-operator">-&gt;</span><span class="cm-variable">delete</span>(); <span class="cm-tab">    </span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  }</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  }</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  }</span></pre><pre class=""><span style="padding-right: 0.1px;">  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-keyword">public</span> <span class="cm-keyword">function</span> <span class="cm-variable">taskB</span>(<span class="cm-variable">Job</span> <span class="cm-variable-2">$job</span>,<span class="cm-variable-2">$data</span>){</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  <span class="cm-variable-2">$isJobDone</span> <span class="cm-operator">=</span> <span class="cm-variable-2">$this</span><span class="cm-operator">-&gt;</span><span class="cm-variable">_doTaskA</span>(<span class="cm-variable-2">$data</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">if</span> (<span class="cm-variable-2">$isJobDone</span>) {</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-variable-2">$job</span><span class="cm-operator">-&gt;</span><span class="cm-variable">delete</span>();</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">print</span>(<span class="cm-string">"Info: TaskB of Job MultiTask has been done and deleted"</span>.<span class="cm-string">"\n"</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  }<span class="cm-keyword">else</span>{</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">if</span> (<span class="cm-variable-2">$job</span><span class="cm-operator">-&gt;</span><span class="cm-variable">attempts</span>() <span class="cm-operator">&gt;</span> <span class="cm-number">2</span>) {</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-variable-2">$job</span><span class="cm-operator">-&gt;</span><span class="cm-variable">release</span>(); <span class="cm-tab">   </span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  }</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  }</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  }</span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-keyword">private</span> <span class="cm-keyword">function</span> <span class="cm-variable">_doTaskA</span>(<span class="cm-variable-2">$data</span>) {</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">print</span>(<span class="cm-string">"Info: doing TaskA of Job MultiTask "</span>.<span class="cm-string">"\n"</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">return</span> <span class="cm-atom">true</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  }</span></pre><pre class=""><span style="padding-right: 0.1px;">  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-keyword">private</span> <span class="cm-keyword">function</span> <span class="cm-variable">_doTaskB</span>(<span class="cm-variable-2">$data</span>) {</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">print</span>(<span class="cm-string">"Info: doing TaskB of Job MultiTask "</span>.<span class="cm-string">"\n"</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">return</span> <span class="cm-atom">true</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  }</span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 1104px;"></div></div></div></pre></li></ul><h4><a name='header-c346' class='md-header-anchor '></a>2.6 消息的延迟执行与定时执行</h4><p>延迟执行，相对于即时执行，是用来限制某个任务的最早可执行时刻。在到达该时刻之前，该任务会被跳过。</p><p>可以利用该功能实现<strong>定时任务</strong>。</p><p>使用方式：</p><ul><li>在生产者业务代码中：</li></ul><pre class="md-fences md-end-block" lang="php"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">// 即时执行</span></span></pre></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-variable-2">$isPushed</span> <span class="cm-operator">=</span> <span class="cm-variable">Queue</span>::<span class="cm-variable">push</span>(<span class="cm-variable-2">$jobHandlerClassName</span>, <span class="cm-variable-2">$jobDataArr</span>, <span class="cm-variable-2">$jobQueueName</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">// 延迟 2 秒执行</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-variable-2">$isPushed</span> <span class="cm-operator">=</span> <span class="cm-variable">Queue</span>::<span class="cm-variable">later</span>( <span class="cm-number">2</span>, <span class="cm-variable-2">$jobHandlerClassName</span>, <span class="cm-variable-2">$jobDataArr</span>, <span class="cm-variable-2">$jobQueueName</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">// 延迟到 2017-02-18 01:01:01 时刻执行</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-variable-2">$time2wait</span> <span class="cm-operator">=</span> <span class="cm-builtin">strtotime</span>(<span class="cm-string">'2017-02-18 01:01:01'</span>) <span class="cm-operator">-</span> <span class="cm-builtin">strtotime</span>(<span class="cm-string">'now'</span>);<span class="cm-tab">   </span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-variable-2">$isPushed</span> <span class="cm-operator">=</span> <span class="cm-variable">Queue</span>::<span class="cm-variable">later</span>(<span class="cm-variable-2">$time2wait</span>,<span class="cm-variable-2">$jobHandlerClassName</span>, <span class="cm-variable-2">$jobDataArr</span>, <span class="cm-variable-2">$jobQueueName</span>);</span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 161px;"></div></div></div></pre><ul><li>在消费者类中：</li></ul><pre class="md-fences md-end-block" lang="php"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">// 重发，即时执行</span></span></pre></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-variable-2">$job</span><span class="cm-operator">-&gt;</span><span class="cm-variable">release</span>();</span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">// 重发，延迟 2 秒执行</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-variable-2">$job</span><span class="cm-operator">-&gt;</span><span class="cm-variable">release</span>(<span class="cm-number">2</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">// 延迟到 2017-02-18 01:01:01 时刻执行</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-variable-2">$time2wait</span> <span class="cm-operator">=</span> <span class="cm-builtin">strtotime</span>(<span class="cm-string">'2017-02-18 01:01:01'</span>) <span class="cm-operator">-</span> <span class="cm-builtin">strtotime</span>(<span class="cm-string">'now'</span>);</span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-variable-2">$job</span><span class="cm-operator">-&gt;</span><span class="cm-variable">release</span>(<span class="cm-variable-2">$time2wait</span>);</span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 161px;"></div></div></div></pre><ul><li>在命令行中：</li></ul><pre class="md-fences md-end-block" lang="bash"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;">//如果消费者类的fire()方法抛出了异常且任务未被删除时，将自动重发该任务，重发时，会设置其下次执行前延迟多少秒,默认为0</span></pre></div><pre class=""><span style="padding-right: 0.1px;">php think queue:work <span class="cm-attribute">--delay</span> <span class="cm-number">3</span>  </span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 69px;"></div></div></div></pre><h4><a name='header-c368' class='md-header-anchor '></a>2.7 消息的重发</h4><p>thinkphp-queue 中，消息的重发时机有3种：</p><ul><li>2.7.1 在消费者类中手动重发：</li></ul><pre class="md-fences md-end-block" lang="php"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-keyword">if</span>( <span class="cm-variable-2">$isJobDone</span> <span class="cm-operator">===</span> <span class="cm-atom">false</span>){</span></pre></div><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-variable-2">$job</span><span class="cm-operator">-&gt;</span><span class="cm-variable">release</span>();</span></pre><pre class=""><span style="padding-right: 0.1px;">}</span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 69px;"></div></div></div></pre><ul><li><p>2.7.2 work进程自动重发，需同时满足以下两个条件</p><ul><li>消费者类的 fire() 方法抛出了异常</li><li>任务未被删除</li></ul></li><li><p>2.7.3 当配置了 expire 不为 <code>null</code> 时，work 进程内部每次查询可用任务之前，会先自动重发已过期的任务。</p></li></ul><blockquote><p>补充：</p><p>在database 模式下，2.7.1 和 2.7.2 中的重发逻辑是先删除原来的任务，然后插入一个新的任务。2.7.3 中的重发时机是直接更新原任务。</p><p>而在redis 模式下，3种重发都是先删除再插入。</p><p>不管是哪种重发方式，重发之后，任务的已尝试次数会在原来的基础上 +1 。</p></blockquote><p>此外，消费者类中需要注意，如果 <code>fire()</code> 方法中可能抛出异常，那么</p><ul><li>如果不需要自动重发的话， 请在抛出异常之前将任务删除 <code>$job-&gt;delete()</code> ，以免产生bug。</li><li>如果需要自动重发的话，请直接抛出异常，不要在 <code>fire()</code> 方法中又手动使用 <code>$job-&gt;release()</code> , 这样会导致该任务被重发两次，产生两个一样的新任务。</li></ul><h4><a name='header-c408' class='md-header-anchor '></a>2.8 任务的失败回调及告警</h4><p>当同时满足以下条件时，将触发任务失败回调：</p><ul><li>命令行的 <code>--tries</code> 参数的值大于0</li><li>任务的已尝试次数大于 命令行的 <code>--tries</code> 参数</li><li>开发者添加了 <code>queue_failed</code> 事件标签及其对应的回调代码</li><li>消费者类中定义了 <code>failed()</code> 方法，用于接收任务失败的通知</li></ul><p>注意， <code>queue_failed</code> 标签需要在安装了 <code>thinkphp-queue</code> 之后 <strong>手动</strong> 去 <code>\application\tags.php</code> 文件中添加。</p><p><strong>注意：该版本有<a href='https://github.com/top-think/think-queue/issues/10'>bug</a>，若想实现失败任务回调功能，需要先修改位于 <code>think-queue\src\queue\Worker.php</code> 中的 <code>logFailedJob</code>方法 , 修改方式如下:</strong></p><pre class="md-fences md-end-block" lang="php"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">/**</span></span></pre></div><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-comment">* Log a failed job into storage.</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-comment">* @param  \Think\Queue\Job $job</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-comment">* @return array</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-comment">*/</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-keyword">protected</span> <span class="cm-keyword">function</span> <span class="cm-variable">logFailedJob</span>(<span class="cm-variable">Job</span> <span class="cm-variable-2">$job</span>)</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  {</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  <span class="cm-comment">// 将原来的 queue.failed' 修改为 'queue_failed' 才可以触发任务失败回调</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">if</span> (<span class="cm-variable">Hook</span>::<span class="cm-variable">listen</span>(<span class="cm-string">'queue.failed'</span>, <span class="cm-variable-2">$job</span>, <span class="cm-atom">null</span>, <span class="cm-atom">true</span>)) {  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-variable-2">$job</span><span class="cm-operator">-&gt;</span><span class="cm-variable">delete</span>();</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-variable-2">$job</span><span class="cm-operator">-&gt;</span><span class="cm-variable">failed</span>();</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  }</span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">return</span> [<span class="cm-string">'job'</span> <span class="cm-operator">=&gt;</span> <span class="cm-variable-2">$job</span>, <span class="cm-string">'failed'</span> <span class="cm-operator">=&gt;</span> <span class="cm-atom">true</span>];</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  } </span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 345px;"></div></div></div></pre><p>首先，我们添加 <code>queue_failed</code> 事件标签,  及其对应的回调方法</p><pre class="md-fences md-end-block" lang="php"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">// 文件路径： \application\tags.php</span></span></pre></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">// 应用行为扩展定义文件</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-keyword">return</span> [</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-comment">// 应用初始化</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-string">'app_init'</span> &nbsp; &nbsp; <span class="cm-operator">=&gt;</span> [],</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-comment">// 应用开始</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-string">'app_begin'</span> &nbsp;  <span class="cm-operator">=&gt;</span> [],</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-comment">// 模块初始化</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-string">'module_init'</span>  <span class="cm-operator">=&gt;</span> [],</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-comment">// 操作开始执行</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-string">'action_begin'</span> <span class="cm-operator">=&gt;</span> [],</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-comment">// 视图内容过滤</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-string">'view_filter'</span>  <span class="cm-operator">=&gt;</span> [],</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-comment">// 日志写入</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-string">'log_write'</span> &nbsp;  <span class="cm-operator">=&gt;</span> [],</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-comment">// 应用结束</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-string">'app_end'</span> &nbsp; &nbsp;  <span class="cm-operator">=&gt;</span> [],</span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-comment">// 任务失败统一回调,有四种定义方式</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-string">'queue_failed'</span><span class="cm-operator">=&gt;</span> [</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-comment">// 数组形式，[ 'ClassName' , 'methodName']</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  [<span class="cm-string">'application\\behavior\\MyQueueFailedLogger'</span>, <span class="cm-string">'logAllFailedQueues'</span>]</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-comment">// 字符串(静态方法)，'StaicClassName::methodName'</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-comment">// 'MyQueueFailedLogger::logAllFailedQueues' &nbsp; </span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp;  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-comment">// 字符串(对象方法)，'ClassName'，此时需在对应的ClassName类中添加一个名为 queueFailed 的方法</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-comment">// 'application\\behavior\\MyQueueFailedLogger'</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-comment">// 闭包形式</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-comment">/*</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-comment">function( &amp;$jobObject , $extra){</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-comment">// var_dump($jobObject);</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-comment">return true;</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-comment">}</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-comment">*/</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  ]</span></pre><pre class=""><span style="padding-right: 0.1px;">];</span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 897px;"></div></div></div></pre><p>这里，我们选择数组形式的回调方式，新增 <code>\application\behavior\MyQueueFailedLogger</code> 类，添加一个 <code>logAllFailedQueues()</code> 方法</p><pre class="md-fences md-end-block" lang="php"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-operator">&lt;?</span><span class="cm-variable">php</span></span></pre></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">/**</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-comment">* 文件路径: \application\behavior\MyQueueFailedLogger.php</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-comment">* 这是一个行为类，用于处理所有的消息队列中的任务失败回调</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-comment">*/</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-keyword">namespace</span> <span class="cm-variable">application\behavior</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-keyword">class</span> <span class="cm-variable">MyQueueFailedLogger</span> {</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-keyword">const</span> <span class="cm-variable">should_run_hook_callback</span> <span class="cm-operator">=</span> <span class="cm-atom">true</span>;</span></pre><pre class=""><span style="padding-right: 0.1px;">  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-comment">/**</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; <span class="cm-comment">* @param $jobObject &nbsp; \think\queue\Job &nbsp; //任务对象，保存了该任务的执行情况和业务数据</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; <span class="cm-comment">* @return bool &nbsp; &nbsp; true &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  //是否需要删除任务并触发其failed() 方法</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; <span class="cm-comment">*/</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-keyword">public</span> <span class="cm-keyword">function</span> <span class="cm-variable">logAllFailedQueues</span>(<span class="cm-operator">&amp;</span><span class="cm-variable-2">$jobObject</span>){</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  <span class="cm-variable-2">$failedJobLog</span> <span class="cm-operator">=</span> [</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-string">'jobHandlerClassName'</span> &nbsp; <span class="cm-operator">=&gt;</span> <span class="cm-variable-2">$jobObject</span><span class="cm-operator">-&gt;</span><span class="cm-variable">getName</span>(), <span class="cm-comment">// 'application\index\job\Hello'</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-string">'queueName'</span> <span class="cm-operator">=&gt;</span> <span class="cm-variable-2">$jobObject</span><span class="cm-operator">-&gt;</span><span class="cm-variable">getQueue</span>(),<span class="cm-tab">  </span><span class="cm-tab">    </span><span class="cm-tab">    </span> &nbsp; <span class="cm-comment">// 'helloJobQueue'<span class="cm-tab">   </span> </span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-string">'jobData'</span> &nbsp; <span class="cm-operator">=&gt;</span> <span class="cm-variable-2">$jobObject</span><span class="cm-operator">-&gt;</span><span class="cm-variable">getRawBody</span>()[<span class="cm-string">'data'</span>],  <span class="cm-comment">// '{'a': 1 }'</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-string">'attempts'</span>  <span class="cm-operator">=&gt;</span> <span class="cm-variable-2">$jobObject</span><span class="cm-operator">-&gt;</span><span class="cm-variable">attempts</span>(), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-comment">// 3</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  ];</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  <span class="cm-builtin">var_export</span>(<span class="cm-variable">json_encode</span>(<span class="cm-variable-2">$failedJobLog</span>,<span class="cm-atom">true</span>));</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="cm-comment">// $jobObject-&gt;release(); &nbsp; &nbsp; //重发任务</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-comment">//$jobObject-&gt;delete(); &nbsp; &nbsp; &nbsp; &nbsp; //删除任务</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;  <span class="cm-comment">//$jobObject-&gt;failed();<span class="cm-tab">   </span>  //通知消费者类任务执行失败</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;  <span class="cm-keyword">return</span> <span class="cm-keyword">self</span>::<span class="cm-variable">should_run_hook_callback</span>; &nbsp; &nbsp; &nbsp; &nbsp; </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  }</span></pre><pre class=""><span style="padding-right: 0.1px;">}</span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 782px;"></div></div></div></pre><p>需要注意该回调方法的返回值：</p><ul><li>返回 true  时，系统会自动删除该任务，并且自动调用消费者类中的 <code>failed()</code> 方法</li><li>返回 false 时，系统不会自动删除该任务，也不会自动调用消费者类中的 <code>failed()</code> 方法，需要开发者另行处理失败任务的删除和通知。</li></ul><p>最后，在消费者类中，添加 <code>failed()</code> 方法</p><pre class="md-fences md-end-block" lang="php"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">/**</span></span></pre></div><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-comment">* 文件路径： \application\index\job\HelloJob.php</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-comment">*/</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span>​</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-comment">/**</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-comment">* 该方法用于接收任务执行失败的通知，你可以发送邮件给相应的负责人员</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-comment">* @param $jobData  string|array|... &nbsp; &nbsp;  //发布任务时传递的 jobData 数据</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-comment">*/</span></span></pre><pre class=""><span style="padding-right: 0.1px;"><span class="cm-keyword">public</span> <span class="cm-keyword">function</span> <span class="cm-variable">failed</span>(<span class="cm-variable-2">$jobData</span>){</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-variable">send_mail_to_somebody</span>() ; </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  </span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp;  <span class="cm-keyword">print</span>(<span class="cm-string">"Warning: Job failed after max retries. job data is :"</span>.<span class="cm-builtin">var_export</span>(<span class="cm-variable-2">$data</span>,<span class="cm-atom">true</span>).<span class="cm-string">"\n"</span>; </span></pre><pre class=""><span style="padding-right: 0.1px;">}</span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 299px;"></div></div></div></pre><p>这样，就可以做到任务失败的<strong>记录</strong>与<strong>告警</strong></p><h4><a name='header-c449' class='md-header-anchor '></a>2.9 处理过期的任务</h4><p>过期这个概念用文字比较难描述清楚，建议先看一下 <strong>深入理解</strong> 中 <strong>3.4 消息处理的详细流程图</strong></p><h3><a name='header-c452' class='md-header-anchor '></a>三 深入理解</h3><h4><a name='header-c453' class='md-header-anchor '></a>3.1 thinkphp-queue 中消息与队列的保存方式</h4><ul><li><p>Redis</p><p>在 Redis 中，每一个 队列 都三个key 与之对应 ，以 helloJobQueue 队列举例，其在redis 中的保存方式为：</p><table><thead><tr><th>key名</th><th>类型</th><th>说明</th></tr></thead><tbody><tr><td>queues:helloJobQueue</td><td>List ， 列表</td><td>待执行的任务列表</td></tr><tr><td>queues:helloJobQueue:delayed</td><td>Sorted Set，有序集合</td><td>延迟执行和定时执行的任务集合</td></tr><tr><td>queues:helloJobQueue:reserved</td><td>Sorted Set，有序集合</td><td>执行中的任务集合</td></tr></tbody></table><blockquote><p>使用的<code>:</code>分隔符, 只是用来表示相关key的关联性。本身没有特殊含义。使用分隔符是一种常见的组织key的方式。</p></blockquote><p>其中，在<code>queues:helloJobQueue</code> 列表中，每个元素的形式如下：</p><p><img src='redis%E4%B8%AD%E7%9A%84%E9%98%9F%E5%88%97-queue.png' alt='redis中的队列-queue' /></p><p>在 <code>queues:helloJobQueue:delayed</code> 和 <code>queues:helloJobQueue:delayed</code> 有序集合中，每个元素的形式如下：</p><p><img src='redis%E4%B8%AD%E7%9A%84%E9%98%9F%E5%88%97-queue-reserved.png' alt='redis中的队列-queue-reserved' /></p><p>可以看到，在有序集合中，每个元素代表一个任务，该元素的 Score 为该任务的入队时间戳，任务的 value 为json 格式，保存了任务的执行情况和业务数据。将value decode 为数组后形式如下：</p><pre class="md-fences md-end-block" lang="php"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;">[</span></pre></div><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-string">'job'</span>  <span class="cm-operator">=&gt;</span> <span class="cm-string">'application\\index\\job\\Hello'</span> ,  <span class="cm-comment">// jobHandlerClassName，消费者类的类名 </span></span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-string">'data'</span> <span class="cm-operator">=&gt;</span> [<span class="cm-tab">   </span><span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-tab">    </span>  <span class="cm-comment">// 生产者传入的业务数据</span></span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; <span class="cm-string">'time'</span> <span class="cm-operator">=&gt;</span> <span class="cm-string">'2017-02-18 16:20:10'</span>,</span></pre><pre class=""><span style="padding-right: 0.1px;"> &nbsp; &nbsp; <span class="cm-string">'data'</span> <span class="cm-operator">=&gt;</span> <span class="cm-string">'I have 648 apples'</span></span></pre><pre class=""><span style="padding-right: 0.1px;">  ],</span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-string">'id'</span> &nbsp; <span class="cm-operator">=&gt;</span> <span class="cm-string">'77IasdasadIasdadadadKL8t'</span>,<span class="cm-tab"> </span><span class="cm-comment">// 一个随机的32位字符串</span></span></pre><pre class=""><span style="padding-right: 0.1px;">  <span class="cm-string">'attempts'</span> <span class="cm-operator">=&gt;</span> <span class="cm-number">2</span><span class="cm-tab">   </span><span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-tab">    </span><span class="cm-comment">// 任务的已尝试次数</span></span></pre><pre class=""><span style="padding-right: 0.1px;">]</span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 207px;"></div></div></div></pre><p>redis驱动下，为了实现任务的延迟执行和过期重发，任务将在这三个key中来回转移，详情可见 3.5</p></li><li><p>Database</p><p>在 Database 中，每个任务对应到表中的一行，queue 字段用来区分不同的队列。</p><p>表的字段结构如下:</p><p><img src='%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AD%97%E6%AE%B5%E8%AF%B4%E6%98%8E.png' alt='数据库字段说明' /></p><p>其中，payLoad 字段保存了消息的执行者和业务数据，payLoad 字段采用 json 格式的字符串来保存消息，将其 decode 为数组后形式如下：</p><pre class="md-fences md-end-block" lang="php"> <div class="CodeMirror cm-s-inner CodeMirror-wrap"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 4px;"></div><div class="CodeMirror-hscrollbar" style="left: 0px;"><div style="height: 100%; min-height: 1px; width: 0px;"></div></div><div class="CodeMirror-vscrollbar"><div style="min-width: 1px; height: 0px;"></div></div><div class="CodeMirror-scrollbar-filler"></div><div class="CodeMirror-gutter-filler"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; min-height: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines"><div style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><pre class=""><span style="padding-right: 0.1px;">[</span></pre></div><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-string">'job'</span> &nbsp; <span class="cm-operator">=&gt;</span> <span class="cm-string">'application\\index\\job\\Hello'</span>, <span class="cm-comment">// jobHandlerClassName，消费者类的类名 </span></span></pre><pre class=""><span style="padding-right: 0.1px;"> <span class="cm-string">'data'</span>  <span class="cm-operator">=&gt;</span> <span class="cm-variable">string</span><span class="cm-operator">|</span><span class="cm-keyword">array</span><span class="cm-operator">|</span><span class="cm-variable">integer</span><span class="cm-operator">|</span><span class="cm-variable">object</span> &nbsp; &nbsp; &nbsp; <span class="cm-comment">// 生产者传入的业务数据</span></span></pre><pre class=""><span style="padding-right: 0.1px;">]</span></pre></div></div></div></div></div><div style="position: absolute; height: 30px; width: 1px; top: 0px;"></div><div class="CodeMirror-gutters" style="display: none; height: 92px;"></div></div></div></pre></li></ul><h4><a name='header-c505' class='md-header-anchor '></a>3.2 thinkphp-queue 的目录结构和类关系图</h4><p><img src='thinkphp-queue%E7%9A%84%E6%96%87%E4%BB%B6%E7%9B%AE%E5%BD%95.png' alt='thinkphp-queue的文件目录' /></p><p>这些类构成了消息队列中的几个角色：</p><table><thead><tr><th>角色</th><th>类名</th><th>说明</th></tr></thead><tbody><tr><td>命令行</td><td>Command + Worker</td><td>负责解析命令行参数，控制队列的启动，重启</td></tr><tr><td>驱动</td><td>Queue + Connector</td><td>负责队列的创建，以及消息的入队，出队等操作</td></tr><tr><td>任务</td><td>Job</td><td>用于将消息转化为一个任务对象，供消费者使用</td></tr><tr><td>生产者</td><td>业务代码</td><td>负责消息的创建与发布</td></tr><tr><td>消费者</td><td>业务代码</td><td>负责任务的接收与执行</td></tr></tbody></table><p>各个类之间的关系图如下：</p><p><img src='https://blog.huzhongyuan.com/wp-content/uploads/2017/02/thinkphp-queue%25E7%25B1%25BB%25E5%2585%25B3%25E7%25B3%25BB%25E5%259B%25BE.svg' alt='thinkphp-queue类关系图' /></p><h4><a name='header-c539' class='md-header-anchor '></a>3.3 Deamon模式的执行流程</h4><p><img src='https://blog.huzhongyuan.com/wp-content/uploads/2017/02/Daemon%25E6%25A8%25A1%25E5%25BC%258F%25E4%25B8%258E%25E9%259D%259Edaemon%25E6%25A8%25A1%25E5%25BC%258F%25E7%258A%25B6%25E6%2580%2581%25E5%259B%25BE.svg' alt='Daemon模式与非daemon模式状态图' /></p><h4><a name='header-c542' class='md-header-anchor '></a>3.4 Database模式下消息处理的详细流程</h4><p>下图中，展示了database 模式下消息处理的详细流程，redis 驱动下大体类似</p><p><img src='https://blog.huzhongyuan.com/wp-content/uploads/2017/02/Database%25E9%25A9%25B1%25E5%258A%25A8%25E4%25B8%258B%25E6%25B6%2588%25E6%2581%25AF%25E5%25A4%2584%25E7%2590%2586%25E7%259A%2584%25E5%2585%25B7%25E4%25BD%2593%25E6%25B5%2581%25E7%25A8%258B.svg' alt='Database模式下消息获取和执行的具体流程' /></p><h4><a name='header-c547' class='md-header-anchor '></a>3.5 redis 驱动下的任务重发细节</h4><p>在redis驱动下，为了实现任务的延迟执行和过期重发，任务将在这三个key中来回转移。</p><p>在3.4 Database模式下消息处理的消息流程中，我们知道，如果配置的expire 不是null ，那么 thinkphp-queue的work进程每次在获取下一个可执行任务之前，会先尝试重发所有过期的任务。而在redis驱动下，这个步骤则做了更多的事情，详情如下：</p><ol start='' ><li>从 <code>queue:xxx:delayed</code> 的key中查询出有哪些任务在当前时刻已经可以开始执行，然后将这些任务转移到 <code>queue:xxx</code> 的key的尾部。</li><li>从 <code>queue:xxx:reserved</code> 的key中查询出有哪些任务在当前时刻已经过期，然后将这些任务转移到 <code>queue:xxx</code>的key的尾部。</li><li>尝试从 <code>queue:xxx</code> 的key的头部取出一个任务，如果取出成功，那么，将这个任务转移到 <code>queue:xxx:reserved</code> 的key 的头部，同时将这个任务实例化成任务对象，交给消费者去执行。</li></ol><p>用图来表示这个步骤的具体过程如下：</p><p>redis队列中的过期任务重发步骤--执行前：</p><p><img src='https://blog.huzhongyuan.com/wp-content/uploads/2017/02/redis%25E9%2598%259F%25E5%2588%2597%25E4%25B8%25AD%25E7%259A%2584%25E4%25BB%25BB%25E5%258A%25A1%25E7%25AE%25A1%25E7%2590%2586-1.png' alt='redis队列中的过期任务重发步骤-执行前' /></p><p>redis队列中的过期任务重发步骤--执行后：</p><p><img src='https://blog.huzhongyuan.com/wp-content/uploads/2017/02/redis%25E9%2598%259F%25E5%2588%2597%25E4%25B8%25AD%25E7%259A%2584%25E4%25BB%25BB%25E5%258A%25A1%25E7%25AE%25A1%25E7%2590%2586-2.png' alt='redis队列中的过期任务重发步骤--执行后' /></p><h4><a name='header-c572' class='md-header-anchor '></a>3.6 thinkphp-queue的性能</h4><ul><li><p>测试环境 :</p><p>虚拟机 Ubuntu 16.04 ， PHP 7.1 ，TP5，Redis 3.2 ， 双核 I5 6400，3G 内存</p></li><li><p>测试方式 :</p><p>使用 Redis 驱动，在一个控制器中循环推送 40000 条消息到消息队列； </p><p>使用<code>php think queue:work --daemon</code>去消费这些消息，计算推送和消费各自所耗的时间。</p></li></ul><ul><li><p>测试结果 :</p><p>在最简单的逻辑下，平均每秒中可推送8000个消息，平均每秒可消费200个消息。</p></li></ul><p><strong>注意：</strong>由于在测试时，Host 机本身的cpu和内存长期100%，并且虚拟机中的各项服务并未专门调优，因此该测试结果<strong>并不具备参考性</strong>。</p><h4><a name='header-c594' class='md-header-anchor '></a>3.7 thinkphp-queue 的N种错误使用姿势</h4><ul><li><p><strong>3.7.1</strong> 在 消费者类的 <code>fire()</code> 方法中，忘记使用 <code>$job-&gt;delete()</code> 去删除消息，这种情况下，会产生一系列的bug：</p><ul><li><p>配置的 expire 为 <code>null</code> ， 则该任务被执行一次后会永远留在消息队列中，占用消息队列的空间 , 除非开发者另行处理。</p></li><li><p>配置的 expire <code>不为 null</code> ，该任务在 expire 秒后被认为是过期任务，并被消息队列还原为待执行状态，在消息队列的后面的循环中继续被获取，这时，如果</p><ul><li><p>命令行中的 <code>--tries</code> 参数为0 或者未设置,那么每隔 一段时间该任务就会被执行一次。</p></li><li><p>命令行中的 <code>--tries</code> 参数 n 大于0 ， 那么当这个任务被误执行的次数超过n 时，会由消息队列尝试去触发失败回调事件:</p><ul><li><p>如果开发者没有编写失败处理的回调事件：那么该任务仍然不会被删除，每隔一段时间就会被执行一次。[这个可能属于框架的<a href='https://github.com/top-think/think-queue/issues/10'>Bug</a>] , </p></li><li><p>如果编写了失败回调事件</p><ul><li><p>回调事件中删除了任务，则这个任务被误执行了 n 次。</p></li><li><p>回调事件中未删除任务，这时，如果：</p><ul><li>回调事件返回值是 false，那么该任务仍然不会被删除，每隔一段时间就会被执行一次</li><li>回调事件返回值是 true， 那么该任务会先被删除，然后触发消费者类的 failed() 方法，如果在 failed() 方法中设置了告警，那么这个告警就是一次误报。</li></ul></li></ul></li></ul></li></ul></li></ul><p>因此，在 使用 thinkphp-queue 时，请记得：</p><ul><li><strong>任务完成后, 使用 <code>$job-&gt;delete()</code> 删除任务</strong></li><li>在消费者类的 <code>fire()</code> 方法中，使用 <code>$job-&gt;attempt()</code>  检查任务已执行次数，对于次数异常的，作相应的处理。</li><li>在消费者类的 <code>fire()</code> 方法中，根据业务数据来判断该任务是否已经执行过，以避免该任务被重复执行。</li><li>编写失败回调事件，将事件中失败的任务及时通知给开发人员。</li></ul></li></ul><ul><li><strong>3.7.2</strong> 使用了 <code>queue:work --daemon</code> ，但更新代码后没有使用 <code>queue:restart</code> 重启 work 进程, 使得 work  进程中的代码与最新的代码不同，出现各种问题。</li><li><strong>3.7.3</strong> 使用了 <code>queue:work --daemon</code> ，但是消费者类的 fire() 方法中存在死循环，或 <code>sleep(n)</code> 等逻辑，导致消息队列被堵塞；或者使用了 <code>exit()</code> , <code>die()</code> 这样的逻辑，导致work进程直接终止 。</li><li><strong>3.7.4</strong> 配置的 expire 为 <code>null</code> ，这时如果采用的是 Redis 驱动且使用了延迟功能，如 <code>later(n)</code>  ， <code>release(n)</code> 方法或者 <code>--delay</code> 参数不为0 ， 那么将导致被延迟的任务永远无法处理。(这个可能属于框架的<a href='https://github.com/top-think/think-queue/issues/12'>Bug</a>)</li><li><strong>3.7.5</strong> 配置的 expire 为<code>null</code> ，但并没有自行处理过期的任务，导致过期的任务得不到处理，且一直占用消息队列的空间。</li><li><strong>3.7.6</strong> 配置的 expire <code>不为null</code> ，但配置的 expire 时间太短，以至于  expire 时间 &lt; 消费者的 <code>fire()</code> 方法所需时间 +  删除该任务所需的时间 ，那么任务将被误认为执行超时，从而被消息队列还原为待执行状态。</li><li><strong>3.7.7</strong> 使用 <code>Queue::push($jobHandlerClassName , $jobData, $jobQueueName );</code> 推送任务时，<code>$jobData</code> 中包含未序列化的对象。这时，在消费者端拿到的 <code>$jobData</code> 中拿到的是该对象的public 属性的键值对数组。因此，需要在推送前手动序列化对象，在消费者端再手动反序列化还原为对象。</li></ul><h3><a name='header-c668' class='md-header-anchor '></a>四 拓展</h3><h4><a name='header-c669' class='md-header-anchor '></a>4.1 队列的稳定性和拓展性</h4><ul><li>稳定性：不管是 listen 模式还是 work 模式，都建议使用 supervisor 或者 自定义的cron 脚本，去定时检查 work 进程是否正常</li><li>拓展性： 当某个队列的消费者不足时，再给这个队列添加 work进程即可。</li></ul><h4><a name='header-c677' class='md-header-anchor '></a>4.2 消息队列的可视化管理工具</h4><ul><li>队列管理，队列的列表，队列的 work 进程数量控制，队列的任务数量变化趋势  //TBD</li><li>任务管理，任务的列表，添加/<strong>撤回</strong>/查询任务，修改任务的 执行者/执行时间/优先级/数据 等  //TBD</li></ul><h4><a name='header-c685' class='md-header-anchor '></a>4.2 编写自定义的 thinkphp-queue 驱动</h4><p>//TBD</p><h4><a name='header-c688' class='md-header-anchor '></a>4.3 编写消息队列的单元测试</h4><p>//TBD</p><h4><a name='header-c691' class='md-header-anchor '></a>4.4 与其他PHP消息队列库的对比</h4><p>TP5的消息队列与Laravel的消息队列比较相似，下面是与laravel 中的消息队列的一些对比：</p><table><thead><tr><th></th><th>thinkphp-queue (v1.1.2)</th><th>laravel-queue (v5.3)</th></tr></thead><tbody><tr><td>内置的驱动</td><td>Database，Redis，Sync，TopThink</td><td>Database，Redis, Sync(在laravel中称为 null)。</td></tr><tr><td>Redis驱动要求</td><td>安装redis的C扩展</td><td>安装 predis 包 + LUA脚本</td></tr><tr><td>推送任务</td><td>允许推送 消费者类名，消费者对象</td><td>允许推送消费者类名，消费者对象，闭包</td></tr><tr><td>失败任务处理</td><td>触发失败回调事件 (有Bug)</td><td>触发失败回调事件 + 移动任务到 failed_jobs表?</td></tr><tr><td>消息订阅</td><td>subscribe 命令+ Topthink驱动(注：未实现/未提供)</td><td>subscribe 命令 + 安装IronMQ 驱动</td></tr><tr><td>删除任务</td><td>消费者类中手动删除</td><td>任务完成后自动删除</td></tr><tr><td>推送到多个队列</td><td>需自己实现</td><td>原生支持</td></tr><tr><td>延迟执行</td><td>支持 (有Bug)</td><td>支持</td></tr><tr><td>消息重发</td><td>支持</td><td>支持</td></tr><tr><td>检查已执行次数</td><td>原生支持</td><td>需在消费者类中显式 use 相关的 trait</td></tr><tr><td>执行方式</td><td>work 模式 + listen 模式</td><td>work 模式 + listen 模式</td></tr><tr><td>进程命令</td><td>开启，停止，重启</td><td>开启，停止，重启</td></tr><tr><td>任务命令</td><td>无</td><td>展示失败任务列表，重试某个失败任务，删除某个失败任务</td></tr><tr><td>支持的事件</td><td>失败回调事件</td><td>失败回调事件，支持消费前事件，消费后事件</td></tr></tbody></table><h3><a name='header-c755' class='md-header-anchor '></a>五 待讨论的问题</h3><h4><a name='header-c756' class='md-header-anchor '></a>5.1 thinkphp-queue 中消息名称与消费者类名的绑定怎么实现？</h4><h4><a name='header-c757' class='md-header-anchor '></a>5.1 消息中的业务数据的格式如何约定？</h4><p></p><p></p><p></p><p></p><p></p><p></p></div>
</body>
</html>